الگوهای جدید

انیمیشن‌ها، قالب‌بندی، مؤلفه‌ها و الگوهای چیدمان بیشتر زنده و آماده هستند تا به شروع یا الهام بخشیدن به UI و UX شما کمک کنند.

من برای به اشتراک گذاشتن بسیاری از الگوهای جدید web.dev هیجان زده هستم! این موارد اضافه شده از نمایش چالش‌های رابط کاربری گرافیکی هستند که در آن استراتژی‌های خود را در مورد نحوه ساخت اجزای مختلف و نیازهای مشترک رابط به اشتراک می‌گذارم، سپس ارسال‌های کاربران را برای وظایف مشابه جمع‌آوری می‌کنم و به همه ما کمک می‌کنم دیدگاه‌های خود را در مورد چگونگی حل آنها رشد دهیم.

به نظر می رسد چالش های رابط کاربری گرافیکی به خوبی در الگوها قرار می گیرند:

HTML

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

CSS


        @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%)
  }
}
        

JS


        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. موضوع بندی

به علاوه، پنج الگوی جدید به الگوهای Layout موجود اضافه شد.

اجزاء

پشتیبانی از گرافیک که دارای اجزای نمونه اولیه رنگارنگ در یک طرح شبکه است.

صفحه فرود الگوهای مؤلفه را مشاهده کنید یا هر کدام را به صورت جداگانه بررسی کنید:

  1. پودرهای سوخاری
  2. دکمه ها
  3. چرخ فلک
  4. گفتگو
  5. منوی بازی
  6. نوار بارگیری
  7. پیشین رسانه
  8. چند انتخاب کنید
  9. تنظیمات
  10. سیدناو
  11. دکمه های تقسیم
  12. داستان ها
  13. SVG Favicon
  14. تعویض
  15. زبانه ها
  16. نان تست

در اینجا یک پیش نمایش از الگوی دکمه تقسیم است:

HTML

<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>

CSS


        .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%;
  }
}
        

JS


        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. کلمات تعاملی

در اینجا پیش نمایشی از الگوی حروف متحرک آورده شده است:

HTML

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

CSS


        @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;
  }
}
        

JS


        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 است.

در اینجا پیش نمایشی از الگوی طرح های رنگی آمده است:

HTML

<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>

CSS


        * {
  /* 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);
}
        

JS


        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. Autobot
  2. مرکز محتوا
  3. مرکز کرکی
  4. فلکس ملایم
  5. پاپ و پلاپ

هر نسخه ی نمایشی دارای یک دسته برای تغییر اندازه ظرف و یک دکمه برای اضافه کردن یک فرزند به طرح بندی است. همانطور که در مقاله توضیح داده شد، اینها به شما کمک می‌کنند تا نقاط قوت و ضعف تکنیک‌های مختلف مرکزی‌سازی را که وب ارائه می‌دهد احساس کنید. به علاوه، آنها نام های جالبی دارند.

در اینجا مقاله تعیین شده "برنده" کاوش مرکز، Gentle Flex است:

HTML

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

CSS


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

بسته شدن

امیدوارم این الگوهای جدید به آموزش تکنیک‌های جدید، الهام بخشیدن به شما، ارائه بینش‌هایی در مورد دسترسی و به طور کلی شما را در ساخت UI کمک کند. منتظر الگوهای بیشتر باشید زیرا تیم Chrome همچنان به افزودن به این مجموعه ها ادامه می دهد.