أنماط جديدة

تكون الرسوم المتحركة والموضوعات والمكونات والمزيد من أنماط التخطيط مباشرة وجاهزة للمساعدة في البدء أو إلهام واجهة المستخدم وتجربة المستخدم الخاصة بك أو إلهامها.

يسرّنا مشاركة العديد من أنماط 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. التصميم

بالإضافة إلى ذلك، تمت إضافة خمسة أنماط جديدة إلى أنماط التنسيق الحالية.

المكوّنات

رسم داعم يحتوي على مكونات ذات نماذج أوّلية ملونة في تخطيط
شبكي.

اطّلع على الصفحة المقصودة لأنماط المكوّنات أو اطّلع على كل صفحة على حدة:

  1. أشرطة التنقل
  2. الأزرار
  3. لوحة عرض دوّارة
  4. مربّع حوار
  5. قائمة الألعاب
  6. شريط التحميل
  7. أداة تمرير الوسائط
  8. اختيار متعدد
  9. الإعدادات
  10. Sidenav
  11. أزرار التقسيم
  12. قصص
  13. رمز SVG المفضّل
  14. التبديل
  15. علامات التبويب
  16. Toast

في ما يلي معاينة لنمط زر التقسيم:

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. Fluffy Center
  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;
}
        

الخاتمة

آمل أن تساعدك هذه الأنماط الجديدة في تعليمك أساليب جديدة، وإلهامك، وتقديم رؤى حول إمكانية الوصول، والحفاظ بشكل عام على حماسك بشأن إنشاء واجهة المستخدم. تابِعنا للاطّلاع على المزيد من الأنماط، مع استمرار فريق Chrome في إضافة السلع إلى هذه المجموعات.