Новые шаблоны

Анимации, темы, компоненты и другие шаблоны макетов уже доступны и готовы помочь начать или вдохновить ваш пользовательский интерфейс и UX.

Я рад поделиться множеством новых шаблонов web.dev ! Эти дополнения взяты из шоу «Проблемы с графическим интерфейсом» , где я делюсь своими стратегиями создания различных компонентов и общих потребностей в интерфейсах, а затем собираю предложения пользователей по одним и тем же задачам и помогаю всем нам расширить наши взгляды на способы их решения.

Оказывается, проблемы с графическим интерфейсом прекрасно вписываются в шаблоны:

<h1 split-by="word" word-animation="hover">
  hover the words
</h1>

       
@media (prefers-reduced-motion:no-preference) {
 
[word-animation] {
   
display: inline-flex;
   
flex-wrap: wrap;
   
gap: 1ch
 
}
}

@media (prefers-reduced-motion:no-preference) and (hover) {
 
[word-animation=hover] {
   
overflow: hidden;
   
overflow: clip
 
}

 
[word-animation=hover]>span {
   
transition: transform .3s ease;
   
cursor: pointer
 
}

 
[word-animation=hover]>span:not(:hover) {
   
transform: translateY(50%)
 
}
}
       

       
const span = (text, index) => {
 
const node = document.createElement('span')

  node
.textContent = text
  node
.style.setProperty('--index', index)
 
 
return node
}

export const byWord = text =>
  text
.split(' ').map(span)

const {matches:motionOK} = window.matchMedia(
 
'(prefers-reduced-motion: no-preference)'
)

if (motionOK) {
 
const splitTargets = document.querySelectorAll('[split-by]')

  splitTargets
.forEach(node => {
    let nodes
= byWord(node.innerText)

   
if (nodes)
      node
.firstChild.replaceWith(...nodes)
 
})
}
       

Теперь их можно встраивать в публикации (как указано выше), объединять для удобства просмотра и вдохновения, а также добавлены новые категории, в которые другие участники могут добавлять свои шаблоны. Осмотритесь вокруг, возьмите код: все это для вас.

Обзор

Три новые категории узоров:

  1. Компоненты
  2. Анимации
  3. Тематика

Кроме того, к существующим шаблонам макета добавлено пять новых шаблонов.

Компоненты

Вспомогательная графика с красочными прототипами компонентов в виде сетки.

Просмотрите целевую страницу шаблонов компонентов или проверьте каждый по отдельности:

  1. Панировочные сухари
  2. Кнопки
  3. Карусель
  4. Диалог
  5. Меню игры
  6. Панель загрузки
  7. Медиа-скроллер
  8. Выбор из нескольких вариантов
  9. Настройки
  10. Сиденав
  11. Разделенные кнопки
  12. Истории
  13. SVG-фавикон
  14. Выключатель
  15. Вкладки
  16. Тост

Вот предварительный просмотр шаблона разделенной кнопки:

<div class="gui-split-button">
 
<button>View Cart</button>
 
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
   
<svg aria-hidden="true" viewBox="0 0 20 20">
     
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
   
</svg>
   
<ul class="gui-popup">
     
<li><button>
       
<svg aria-hidden="true" viewBox="0 0 24 24">
         
<path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
       
</svg>
        Checkout
     
</button></li>
     
<li><button>
       
<svg aria-hidden="true" viewBox="0 0 24 24">
         
<path d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" />
       
</svg>
        Quick Pay
     
</button></li>
     
<li><button>
       
<svg aria-hidden="true" viewBox="0 0 24 24">
         
<path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
       
</svg>
        Save for later
     
</button></li>
   
</ul>
 
</span>
</div>

<div class="gui-split-button">
 
<button>Send</button>
 
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
   
<svg aria-hidden="true" viewBox="0 0 20 20">
     
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
   
</svg>
   
<ul class="gui-popup">
     
<li><button>
       
<svg aria-hidden="true" viewBox="0 0 24 24">
         
<path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
       
</svg>
        Schedule for later
     
</button></li>
     
<li><button>
       
<svg aria-hidden="true" viewBox="0 0 24 24">
         
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
       
</svg>
        Delete
     
</button></li>
     
<li><button>
       
<svg aria-hidden="true" viewBox="0 0 24 24">
         
<path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
       
</svg>
        Save draft
     
</button></li>
   
</ul>
 
</span>
</div>

<div class="gui-split-button">
 
<button>Squash</button>
 
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
   
<svg aria-hidden="true" viewBox="0 0 20 20">
     
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
   
</svg>
   
<ul class="gui-popup">
     
<li><button>
        Create a merge commit
     
</button></li>
     
<li><button>
        Rebase
     
</button></li>
   
</ul>
 
</span>
</div>

       
.gui-split-button {
 
--theme:        hsl(220 75% 50%);
 
--theme-hover:  hsl(220 75% 45%);
 
--theme-active: hsl(220 75% 40%);
 
--theme-text:   hsl(220 75% 25%);
 
--theme-border: hsl(220 50% 75%);
 
--ontheme:      hsl(220 90% 98%);
 
--popupbg:      hsl(220 0% 100%);

 
--border: 1px solid var(--theme-border);
 
--radius: 6px;
 
--in-speed: 500ms;
 
--out-speed: 100ms;

 
display: inline-flex;
 
border-radius: var(--radius);
 
background: var(--theme);
 
color: var(--ontheme);
 
fill: var(--ontheme);

 
touch-action: manipulation;
 
user-select: none;
 
-webkit-tap-highlight-color: transparent;

 
@media (--dark) {
   
--theme:        hsl(220 50% 60%);
   
--theme-hover:  hsl(220 50% 65%);
   
--theme-active: hsl(220 75% 70%);
   
--theme-text:   hsl(220 10% 85%);
   
--theme-border: hsl(220 20% 70%);
   
--ontheme:      hsl(220 90% 5%);
   
--popupbg:      hsl(220 10% 30%);
 
}

 
& button {
   
cursor: pointer;
   
appearance: none;
   
background: none;
   
border: none;

   
display: inline-flex;
   
align-items: center;
   
gap: 1ch;
   
white-space: nowrap;

   
font-family: inherit;
   
font-size: inherit;
   
font-weight: 500;

   
padding-block: 1.25ch;
   
padding-inline: 2.5ch;

   
color: var(--ontheme);
   
outline-color: var(--theme);
   
outline-offset: -5px;

   
&:is(:hover, :focus-visible) {
     
background: var(--theme-hover);
     
color: var(--ontheme);

     
& > svg {
       
stroke: currentColor;
       
fill: none;
     
}
   
}

   
&:active {
     
background: var(--theme-active);
   
}
 
}

 
& > button {
   
border-radius: var(--radius) 0 0 var(--radius);

   
@supports (border-start-start-radius: 1px) {
     
border-end-start-radius: var(--radius);
     
border-start-start-radius: var(--radius);
   
}
 
}

 
@media (--light) {
   
& > button,
   
& button:is(:focus-visible, :hover) {
     
text-shadow: 0 1px 0 var(--theme-active);
   
}
   
& > .gui-popup-button > svg,
   
& button:is(:focus-visible, :hover) > svg {
     
filter: drop-shadow(0 1px 0 var(--theme-active));
   
}
 
}
 
 
& svg {
   
inline-size: 2ch;
   
box-sizing: content-box;
   
stroke-linecap: round;
   
stroke-linejoin: round;
   
stroke-width: 2px;
 
}
}

.gui-popup-button {
 
inline-size: 4ch;
 
cursor: pointer;
 
position: relative;
 
display: inline-flex;
 
align-items: center;
 
justify-content: center;
 
border-inline-start: var(--border);
 
border-radius: 0 var(--radius) var(--radius) 0;

 
@supports (border-start-start-radius: 1px) {
   
border-inline-start: var(--border);
   
border-start-end-radius: var(--radius);
   
border-end-end-radius: var(--radius);
 
}

 
&:is(:hover,:focus-within) {
   
background: var(--theme-hover);
 
}

 
/* fixes iOS trying to be helpful */
 
&:focus {
   
outline: none;
 
}

 
&:active {
   
background: var(--theme-active);
 
}
 
 
&:focus-within {
   
& > svg {
     
transition-duration: var(--in-speed);
     
transform: rotateZ(.5turn);
   
}
   
& > .gui-popup {
     
transition-duration: var(--in-speed);
     
opacity: 1;
     
transform: translateY(0);
     
pointer-events: auto;
   
}
 
}

 
@media (--motionOK) {
   
& > svg {
     
transition: transform var(--out-speed) ease;
   
}
   
& > .gui-popup {
     
transform: translateY(5px);

     
transition:
        opacity var
(--out-speed) ease,
        transform var
(--out-speed) ease;
   
}
 
}
}

.gui-popup {
 
--shadow: 220 70% 15%;
 
--shadow-strength: 1%;

 
opacity: 0;
 
pointer-events: none;

 
position: absolute;
 
inset-block-end: 80%;
 
inset-inline-start: -1.5ch;
 
 
list-style-type: none;
 
background: var(--popupbg);
 
color: var(--theme-text);
 
padding-inline: 0;
 
padding-block: .5ch;
 
border-radius: var(--radius);
 
overflow: hidden;
 
display: flex;
 
flex-direction: column;
 
font-size: .9em;
 
transition: opacity var(--out-speed) ease;

 
box-shadow:
   
0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
   
0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
   
0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
   
0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
   
0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
   
0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
 
;

 
/* fixes iOS trying to be helpful */
 
&:focus {outline: none}

 
@media (--dark) {
   
--shadow-strength: 5%;
   
--shadow: 220 3% 2%;

   
& button:not(:focus-visible, :hover) {
     
text-shadow: 0 1px 0 var(--ontheme);
   
}

   
& button:not(:focus-visible, :hover) > svg {
     
filter: drop-shadow(0 1px 0 var(--ontheme));
   
}
 
}

 
@media (width <= 400px) {
   
inset-inline-start: -200%;
 
}

 
& svg {
   
fill: var(--popupbg);
   
stroke: var(--theme);

   
@media (prefers-color-scheme: dark) {
     
stroke: var(--theme-border);
   
}
 
}

 
& button {
   
color: var(--theme-text);
   
width: 100%;
 
}
}
       

       
import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'

const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')

// popup activating roving index for it's buttons
popupButtons
.forEach(element =>
  rovingIndex
({
    element
,
    target
: 'button',
 
}))

// support escape key
popupButtons
.on('keyup', e => {
 
if (e.code === 'Escape')
    e
.target.blur()
})

popupButtons
.on('focusin', e => {
  e
.currentTarget.setAttribute('aria-expanded', true)
})

popupButtons
.on('focusout', e => {
  e
.currentTarget.setAttribute('aria-expanded', false)
})

// respond to any button interaction
splitButtons
.on('click', event => {
 
if (event.target.nodeName !== 'BUTTON') return
  console
.info(event.target.innerText)
})
       

Анимации

Вспомогательное изображение, на котором мяч движется по кривой.

Просмотрите целевую страницу шаблонов анимации или проверьте каждый по отдельности:

  1. Анимированные буквы
  2. Анимированные слова
  3. Интерактивные письма
  4. Интерактивные слова

Вот предварительный просмотр шаблона анимированных букв:

<h1 split-by="letter" letter-animation="breath">
  animated letters
</h1>

       
@keyframes breath {
  from
{
   
animation-timing-function: ease-out;
 
}

  to
{
   
transform: scale(1.25) translateY(-5px) perspective(1px);
   
text-shadow: 0 0 40px var(--glow-color);
   
animation-timing-function: ease-in-out;
 
}
}

@media (prefers-reduced-motion:no-preference) {
 
[letter-animation] > span {
   
display: inline-block;
   
white-space: break-spaces;
 
}

 
[letter-animation=breath] {
   
--glow-color: white;
 
}

 
[letter-animation=breath]>span {
   
animation: breath 1.2s ease calc(var(--index) * 100 * 1ms) infinite alternate;
 
}
}

@media (prefers-reduced-motion:no-preference) and (prefers-color-scheme: light) {
 
[letter-animation=breath] {
   
--glow-color: black;
 
}
}
       

       
const span = (text, index) => {
 
const node = document.createElement('span')

  node
.textContent = text
  node
.style.setProperty('--index', index)
 
 
return node
}

const byLetter = text =>
 
[...text].map(span)

const {matches:motionOK} = window.matchMedia(
 
'(prefers-reduced-motion: no-preference)'
)

if (motionOK) {
 
const splitTargets = document.querySelectorAll('[split-by]')

  splitTargets
.forEach(node => {
    let nodes
= byLetter(node.innerText)

   
if (nodes)
      node
.firstChild.replaceWith(...nodes)
 
})
}
       

Тематика

Поддержка графики с двумя слоями информационной панели: один розовый, другой синий.

Просмотрите целевую страницу шаблонов тем или проверьте каждый по отдельности:

  1. Цветовые Схемы
  2. Переключение темы

Один из шаблонов заключается в создании переключателя тем на стороне клиента, чтобы пользователи могли указать свои предпочтения без прямой привязки к их системным предпочтениям. Другой предназначен для создания системы оформления тем с настраиваемыми свойствами CSS.

Вот предварительный просмотр образца цветовой схемы:

<header>
 
<h3>Scheme</h3>
 
<form id="theme-switcher">
   
<div>
     
<input checked type="radio" id="auto" name="theme" value="auto">
     
<label for="auto">Auto</label>
   
</div>
   
<div>
     
<input type="radio" id="light" name="theme" value="light">
     
<label for="light">Light</label>
   
</div>
   
<div>
     
<input type="radio" id="dark" name="theme" value="dark">
     
<label for="dark">Dark</label>
   
</div>
   
<div>
     
<input type="radio" id="dim" name="theme" value="dim">
     
<label for="dim">Dim</label>
   
</div>
 
</form>
</header>

<main>
 
<section>
   
<div class="surface-samples">
     
<div class="surface1 rad-shadow">1</div>
     
<div class="surface2 rad-shadow">2</div>
     
<div class="surface3 rad-shadow">3</div>
     
<div class="surface4 rad-shadow">4</div>
   
</div>
 
</section>

 
<section>
   
<div class="text-samples">
     
<h1 class="text1">
       
<span class="swatch brand rad-shadow"></span>
        Brand
     
</h1>
     
<h1 class="text1">
       
<span class="swatch text1 rad-shadow"></span>
        Text Color 1
     
</h1>
     
<h1 class="text2">
       
<span class="swatch text2 rad-shadow"></span>
        Text Color 2
     
</h1>
     
<br>
     
<p class="text1">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
     
<p class="text2">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
   
</div>
 
</section>
</main>

       
* {
 
/* brand foundation */
 
--brand-hue: 200;
 
--brand-saturation: 100%;
 
--brand-lightness: 50%;

 
/* light */
 
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
 
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
 
--text2-light: hsl(var(--brand-hue) 30% 30%);
 
--surface1-light: hsl(var(--brand-hue) 25% 90%);
 
--surface2-light: hsl(var(--brand-hue) 20% 99%);
 
--surface3-light: hsl(var(--brand-hue) 20% 92%);
 
--surface4-light: hsl(var(--brand-hue) 20% 85%);
 
--surface-shadow-light: var(--brand-hue) 10% 20%;
 
--shadow-strength-light: .02;

 
/* dark */
 
--brand-dark: hsl(
    var
(--brand-hue)
    calc
(var(--brand-saturation) / 2)
    calc
(var(--brand-lightness) / 1.5)
 
);
 
--text1-dark: hsl(var(--brand-hue) 15% 85%);
 
--text2-dark: hsl(var(--brand-hue) 5% 65%);
 
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
 
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
 
--surface3-dark: hsl(var(--brand-hue) 5%  20%);
 
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
 
--surface-shadow-dark: var(--brand-hue) 50% 3%;
 
--shadow-strength-dark: .8;

 
/* dim */
 
--brand-dim: hsl(
    var
(--brand-hue)
    calc
(var(--brand-saturation) / 1.25)
    calc
(var(--brand-lightness) / 1.25)
 
);
 
--text1-dim: hsl(var(--brand-hue) 15% 75%);
 
--text2-dim: hsl(var(--brand-hue) 10% 61%);
 
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
 
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
 
--surface3-dim: hsl(var(--brand-hue) 5%  30%);
 
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
 
--surface-shadow-dim: var(--brand-hue) 30% 13%;
 
--shadow-strength-dim: .2;
}

:root {
 
color-scheme: light;

 
/* set defaults */
 
--brand: var(--brand-light);
 
--text1: var(--text1-light);
 
--text2: var(--text2-light);
 
--surface1: var(--surface1-light);
 
--surface2: var(--surface2-light);
 
--surface3: var(--surface3-light);
 
--surface4: var(--surface4-light);
 
--surface-shadow: var(--surface-shadow-light);
 
--shadow-strength: var(--shadow-strength-light);
}

@media (prefers-color-scheme: dark) {
 
:root {
   
color-scheme: dark;

   
--brand: var(--brand-dark);
   
--text1: var(--text1-dark);
   
--text2: var(--text2-dark);
   
--surface1: var(--surface1-dark);
   
--surface2: var(--surface2-dark);
   
--surface3: var(--surface3-dark);
   
--surface4: var(--surface4-dark);
   
--surface-shadow: var(--surface-shadow-dark);
   
--shadow-strength: var(--shadow-strength-dark);
 
}
}

[color-scheme="light"] {
 
color-scheme: light;

 
--brand: var(--brand-light);
 
--text1: var(--text1-light);
 
--text2: var(--text2-light);
 
--surface1: var(--surface1-light);
 
--surface2: var(--surface2-light);
 
--surface3: var(--surface3-light);
 
--surface4: var(--surface4-light);
 
--surface-shadow: var(--surface-shadow-light);
 
--shadow-strength: var(--shadow-strength-light);
}

[color-scheme="dark"] {
 
color-scheme: dark;

 
--brand: var(--brand-dark);
 
--text1: var(--text1-dark);
 
--text2: var(--text2-dark);
 
--surface1: var(--surface1-dark);
 
--surface2: var(--surface2-dark);
 
--surface3: var(--surface3-dark);
 
--surface4: var(--surface4-dark);
 
--surface-shadow: var(--surface-shadow-dark);
 
--shadow-strength: var(--shadow-strength-dark);
}

[color-scheme="dim"] {
 
color-scheme: dark;

 
--brand: var(--brand-dim);
 
--text1: var(--text1-dim);
 
--text2: var(--text2-dim);
 
--surface1: var(--surface1-dim);
 
--surface2: var(--surface2-dim);
 
--surface3: var(--surface3-dim);
 
--surface4: var(--surface4-dim);
 
--surface-shadow: var(--surface-shadow-dim);
 
--shadow-strength: var(--shadow-strength-dim);
}

/* READY TO USE! */
.brand {
 
color: var(--brand);
 
background-color: var(--brand);
}

.surface1 {
 
background-color: var(--surface1);
 
color: var(--text2);
}

.surface2 {
 
background-color: var(--surface2);
 
color: var(--text2);
}

.surface3 {
 
background-color: var(--surface3);
 
color: var(--text1);
}

.surface4 {
 
background-color: var(--surface4);
 
color: var(--text1);
}

.text1 {
 
color: var(--text1);
}

p
.text1 {
 
font-weight: 200;
}

.text2 {
 
color: var(--text2);
}
       

       
const switcher = document.querySelector('#theme-switcher')
const doc = document.firstElementChild

switcher
.addEventListener('input', e =>
  setTheme
(e.target.value))

const setTheme = theme =>
  doc
.setAttribute('color-scheme', theme)
       

Новые шаблоны компоновки по центрированию

Просмотрите целевую страницу шаблонов макетов или ознакомьтесь с каждым из них по отдельности:

  1. Автобот
  2. Библиотека компонентов
  3. Пушистый центр
  4. Нежный флекс
  5. Поп-н-плюп

В каждой демонстрации есть дескриптор для изменения размера контейнера и кнопка для добавления дочернего элемента в макет. Как объясняется в статье , они призваны помочь вам почувствовать сильные и слабые стороны различных методов центрирования, предлагаемых в Интернете. Плюс у них забавные имена.

Вот статья, определяющая «победителя» исследования центрирования, Gentle Flex:

<article class="gentle-flex">
 
<h1>Gentle Flex</h1>
</article>

       
.gentle-flex {
 
display: flex;
 
flex-direction: column;
 
align-items: center;
 
justify-content: center;
 
gap: 1ch;
}
       

Заворачивать

Я надеюсь, что эти новые шаблоны помогут вам научить новым методам, вдохновят вас, дадут представление о доступности и в целом поддержат ваш интерес к созданию пользовательского интерфейса. Следите за обновлениями шаблонов , поскольку команда Chrome продолжает пополнять эти коллекции.