Sekmeler bileşeni oluşturma

iOS ve Android uygulamalarındakine benzer bir sekme bileşeninin nasıl oluşturulacağına dair temel bir genel bakış.

Bu yayında, web için duyarlı, birden fazla cihaz girişini destekleyen ve tarayıcılar arasında çalışan bir Sekmeler bileşeni oluşturma konusundaki düşüncelerimi paylaşmak istiyorum. Demoyu deneyin.

Demo

Video izlemeyi tercih ediyorsanız bu yayının YouTube versiyonunu izleyebilirsiniz:

Genel Bakış

Sekmeler, tasarım sistemlerinin yaygın bir bileşenidir ancak birçok şekil ve biçimde olabilir. İlk olarak <frame> öğesi üzerine oluşturulan masaüstü sekmeleri vardı. Şimdi ise içeriği fizik özelliklerine göre canlandıran akıcı mobil bileşenlerimiz var. Hepsi aynı şeyi yapmaya çalışıyor: Yerden tasarruf etmek.

Günümüzde sekmelerle ilgili kullanıcı deneyiminin temel unsuru, bir görüntüleme çerçevesindeki içeriğin görünürlüğünü açıp kapatan düğme gezinme alanıdır. Birçok farklı içerik alanı aynı alanı paylaşır ancak gezinme bölümünde seçilen düğmeye göre koşullu olarak sunulur.

Web'in bileşen kavramına uyguladığı çok çeşitli stiller nedeniyle kolaj oldukça kaotik.
Son 10 yıldaki sekme bileşeni web tasarım stillerinin kolajı

Web taktikleri

Sonuç olarak, birkaç önemli web platformu özelliği sayesinde bu bileşeni oluşturmanın oldukça kolay olduğunu gördüm:

  • Uygun kaydırma durdurma konumlarıyla şık kaydırma ve klavye etkileşimleri için scroll-snap-points
  • Tarayıcı tarafından işlenen sayfa içi kaydırma sabitleme ve paylaşma desteği için URL karma değerleri aracılığıyla derin bağlantılar
  • <a> ve id="#hash" öğe işaretlemesiyle ekran okuyucu desteği
  • Çapraz geçişleri ve anında sayfa içi kaydırmayı etkinleştirmek için prefers-reduced-motion
  • Seçili sekmeyi dinamik olarak vurgulayan ve rengini değiştiren taslak aşamasındaki @scroll-timeline web özelliği

HTML

Temel olarak buradaki kullanıcı deneyimi şöyledir: Bir bağlantıyı tıklayın, URL'nin iç içe yerleştirilmiş sayfa durumunu temsil etmesini sağlayın ve ardından tarayıcı eşleşen öğeye kaydırılırken içerik alanının güncellendiğini görün.

Burada bazı yapısal içerik öğeleri var: bağlantılar ve :target. Bağlantıların listesi için <nav>, <article> öğelerinin listesi için ise <section> kullanmamız gerekir. Her bağlantı hash'i bir bölümle eşleşir ve tarayıcının, sabitleme yoluyla kaydırma yapmasına olanak tanır.

Bağlantı düğmesi tıklandığında odaklanılan içerik kayarak görünür

Örneğin, bir bağlantıyı tıkladığınızda Chrome 89'da :target makalesi otomatik olarak odaklanır ve JS gerekmez. Kullanıcı daha sonra giriş cihazıyla makale içeriğini her zamanki gibi kaydırabilir. İşaretlemede belirtildiği gibi tamamlayıcı içeriktir.

Sekmeleri düzenlemek için aşağıdaki işaretlemeyi kullandım:

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

<a> ve <article> öğeleri arasında href ve id özelliklerini kullanarak şu şekilde bağlantı kurabilirim:

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

Ardından makaleleri farklı miktarlarda lorem ile, bağlantıları ise farklı uzunlukta ve resim kümesinde başlıklarla doldurdum. İçeriklerimiz hazır olduğuna göre sayfa düzenine başlayabiliriz.

Kayan düzenler

Bu bileşende 3 farklı kaydırma alanı türü vardır:

  • Gezinme (pembe) yatay olarak kaydırılabilir.
  • İçerik alanı (mavi) yatay olarak kaydırılabilir.
  • Her makale öğesi (yeşil) dikey olarak kaydırılabilir.
Kaydırma alanlarını ve kaydırma yönünü gösteren, renklerle eşleşen yön oklarının bulunduğu 3 renkli kutu.

Kaydırma işlemiyle ilgili iki farklı öğe türü vardır:

  1. Pencere
    overflow özellik stiline sahip, tanımlanmış boyutları olan bir kutu.
  2. Büyük boyutlu bir yüzey
    Bu düzende, liste kapsayıcılarıdır: gezinme bağlantıları, bölüm makaleleri ve makale içerikleri.

<snap-tabs> düzeni

Seçtiğim üst düzey düzen, flex (Flexbox) oldu. Yönü column olarak ayarladım. Böylece başlık ve bölüm dikey olarak sıralandı. Bu bizim ilk kaydırma penceremiz ve taşma gizli olduğundan her şeyi gizliyor. Başlık ve bölüm, yakında ayrı bölgeler olarak kaydırma efektini kullanacak.

HTML
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
CSS
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

Renkli 3 parşömen diyagramına geri dönerek:

  • <header> artık (pembe) kaydırma kapsayıcısı olmaya hazır.
  • <section>, (mavi) kaydırma kapsayıcısı olmaya hazır.

Aşağıda VisBug ile vurguladığım çerçeveler, kaydırma kapsayıcılarının oluşturduğu pencereleri görmemize yardımcı olur.

Başlık ve bölüm öğelerinin üzerinde, bileşende kapladıkları alanı gösteren parlak pembe renkli yer paylaşımları var.

Sekmeler <header> düzeni

Sonraki düzen de neredeyse aynı: Dikey sıralama oluşturmak için flex kullanıyorum.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

.snap-indicator, bağlantı grubuyla birlikte yatay olarak hareket etmelidir. Bu başlık düzeni, bu aşamayı ayarlamaya yardımcı olur. Burada mutlak konumlandırılmış öğe yok.

nav ve span.indicator öğelerinin üzerinde, bileşende kapladıkları alanı gösteren parlak pembe renkli yer paylaşımları var.

Sırada kaydırma stilleri var. Kaydırma stillerini iki yatay kaydırma alanımız (başlık ve bölüm) arasında paylaşabileceğimizi fark ettim. Bu nedenle, .scroll-snap-x adında bir yardımcı sınıf oluşturdum.

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

Her birinde x ekseninde taşma, aşırı kaydırmayı yakalamak için kaydırma kapsayıcısı, dokunmatik cihazlar için gizli kaydırma çubukları ve son olarak içerik sunum alanlarını kilitlemek için kaydırarak tutturma gerekir. Klavye sekme sıramız erişilebilirdir ve tüm etkileşimler odağı doğal bir şekilde yönlendirir. Kaydırarak tutturma kapsayıcıları, klavyelerinden güzel bir bant tarzı etkileşim de alır.

Sekme başlığı <nav> düzeni

Gezinme bağlantıları, satır sonu olmadan tek bir satırda, dikey olarak ortalanmış şekilde yerleştirilmeli ve her bağlantı öğesi, kaydırarak tutturma kapsayıcısına tutturulmalıdır. 2021 CSS için hızlı çalışma!

HTML
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
CSS
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

Her bağlantı kendi stilini ve boyutunu belirler. Bu nedenle, gezinme düzeninin yalnızca yönü ve akışı belirtmesi gerekir. Gezinme öğelerinin benzersiz genişlikleri, gösterge genişliğini yeni hedefe göre ayarlarken sekmeler arasındaki geçişi eğlenceli hale getirir. Burada kaç öğe olduğuna bağlı olarak tarayıcı, kaydırma çubuğu oluşturur veya oluşturmaz.

Gezinme çubuğunun &quot;a&quot; öğelerinin üzerinde, bileşende kapladıkları alanı ve taştıkları yeri gösteren parlak pembe yer paylaşımları var.

Sekmeler <section> düzeni

Bu bölüm esnek bir öğedir ve alanın baskın tüketicisi olması gerekir. Ayrıca, makalelerin yerleştirileceği sütunlar oluşturması gerekir. CSS 2021 için yine hızlı bir çalışma oldu. block-size: 100%, bu öğeyi üst öğeyi mümkün olduğunca dolduracak şekilde genişletir ve ardından kendi düzeni için üst öğenin genişliğinin 100% kadar olan bir dizi sütun oluşturur. Üst öğe için güçlü kısıtlamalar yazdığımızdan yüzdeler burada çok işe yarar.

HTML
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
CSS
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

Bu, "dikey olarak mümkün olduğunca genişlet" demek gibidir (flex-shrink: 0 olarak ayarladığımız başlığı hatırlayın: Bu genişletme baskısına karşı bir savunmadır) ve tam yükseklikteki bir sütun grubu için satır yüksekliğini ayarlar. auto-flow stili, ızgaraya alt öğeleri her zaman yatay bir çizgide yerleştirmesini söyler. Kaydırma yapılmaz. Bu tam olarak istediğimiz şeydir: Üst pencerenin taşması.

Makale öğelerinin üzerinde, bileşende kapladıkları alanı ve taştıkları yeri gösteren parlak pembe yer paylaşımları var.

Bazen bunları anlamakta zorlanıyorum. Bu bölüm öğesi bir kutuya sığıyor ancak aynı zamanda bir dizi kutu da oluşturdu. Görsellerin ve açıklamaların faydalı olduğunu umuyorum.

Sekmeler <article> düzeni

Kullanıcı, makale içeriğini kaydırabilmeli ve kaydırma çubukları yalnızca taşma olduğunda gösterilmelidir. Bu makale öğeleri düzgün bir şekilde yerleştirilmiş. Aynı anda hem kaydırma üst öğesi hem de kaydırma alt öğesidir. Tarayıcı burada bizim için bazı zorlu dokunma, fare ve klavye etkileşimlerini yönetiyor.

HTML
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
CSS
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

Makalelerin, üst kaydırıcıları içinde sabitlenmesini seçtim. Gezinme bağlantısı öğelerinin ve makale öğelerinin, ilgili kaydırma kapsayıcılarının satır içi başlangıcına oturması çok hoşuma gidiyor. Uyumlu bir ilişki gibi görünüyor ve hissettiriyor.

article öğesi ve alt öğelerinin üzerinde, bileşende kapladıkları alanı ve taştıkları yönü gösteren parlak pembe yer paylaşımları var

Makale, bir ızgara alt öğesidir ve boyutu, kaydırma kullanıcı deneyimi sunmak istediğimiz görüntü alanı olacak şekilde önceden belirlenmiştir. Yani burada yükseklik veya genişlik stillerine ihtiyacım yok, yalnızca nasıl taşacağını tanımlamam gerekiyor. overflow-y değerini auto olarak ayarlıyorum ve ardından kullanışlı overscroll-behavior özelliğiyle kaydırma etkileşimlerini de yakalıyorum.

3 kaydırma alanı özeti

Aşağıda, sistem ayarlarımda "kaydırma çubuklarını her zaman göster"i seçtim. Bu ayar açıkken düzenin çalışmasının iki kat daha önemli olduğunu düşünüyorum. Düzeni ve kaydırma düzenini incelemem de aynı derecede önemli.

3 kaydırma çubuğu gösterilecek şekilde ayarlandı, artık düzen alanını kullanıyor ve bileşenimiz hâlâ harika görünüyor

Bu bileşende kaydırma çubuğu oluğunu görmenin, kaydırma alanlarının nerede olduğunu, destekledikleri yönü ve birbirleriyle nasıl etkileşim kurduklarını net bir şekilde gösterdiğini düşünüyorum. Bu kaydırma penceresi çerçevelerinin her birinin, bir düzen için flex veya grid üst öğesi olduğunu da göz önünde bulundurun.

Geliştirici Araçları bunu görselleştirmemize yardımcı olabilir:

Kaydırma alanlarında, bileşende kapladıkları alanı ve taştıkları yönü gösteren ızgara ve flexbox araç yer paylaşımları bulunur.
Chromium Geliştirici Araçları'nda, bağlantı öğeleriyle dolu flexbox gezinme öğesi düzeni, makale öğeleriyle dolu ızgara bölümü düzeni ve paragraflar ile başlık öğesiyle dolu makale öğeleri gösteriliyor.

Kaydırma düzenleri tamamlandı: tutturma, derin bağlantı oluşturma ve klavyeyle erişim özellikleri eklendi. Kullanıcı deneyimi iyileştirmeleri, stil ve keyif için güçlü bir temel.

Öne çıkan özellik

Kaydırarak tutturulan alt öğeler, yeniden boyutlandırma sırasında kilitli konumlarını korur. Bu sayede JavaScript'in, cihaz döndürüldüğünde veya tarayıcı yeniden boyutlandırıldığında herhangi bir şeyi görünür hale getirmesi gerekmez. Chromium Geliştirici Araçları Cihaz Modu'nda Duyarlı dışında bir mod seçip cihaz çerçevesini yeniden boyutlandırarak deneyin. Öğenin görünürde kaldığını ve içeriğiyle kilitlendiğini görebilirsiniz. Bu özellik, Chromium'un spesifikasyona uygun şekilde güncellenmesinden bu yana kullanılabilir. Bu konuyla ilgili blog yayınını inceleyebilirsiniz.

Animasyon

Buradaki animasyon çalışmasının amacı, etkileşimleri kullanıcı arayüzü geri bildirimiyle net bir şekilde bağlamaktır. Bu, kullanıcının tüm içeriği sorunsuz bir şekilde keşfetmesine yardımcı olur. Hareketleri amaca yönelik ve koşullu olarak ekleyeceğim. Kullanıcılar artık işletim sistemlerinde hareket tercihlerini belirtebiliyor. Ben de arayüzlerimde bu tercihlere yanıt vermekten büyük keyif alıyorum.

Sekme alt çizgisini makale kaydırma konumuyla bağlayacağım. Yakalama, yalnızca güzel bir hizalama sağlamakla kalmaz, aynı zamanda bir animasyonun başlangıç ve bitişini de sabitler. Böylece <nav>, mini harita gibi davranarak içerikle bağlantılı kalır. Kullanıcının hareket tercihini hem CSS hem de JS'den kontrol edeceğiz. Düşünceli davranabileceğiniz birkaç harika yer var.

Kaydırma davranışı

Hem :target hem de element.scrollIntoView() için hareket davranışını iyileştirme fırsatı var. Varsayılan olarak anında gerçekleşir. Tarayıcı yalnızca kaydırma konumunu ayarlar. Peki, kaydırma konumuna aniden geçmek yerine geçiş yapmak istersek ne olur?

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

Burada hareket eklediğimiz ve kullanıcının kontrol etmediği bir hareket (kaydırma gibi) olduğu için bu stili yalnızca kullanıcının işletim sisteminde azaltılmış hareketle ilgili bir tercihi yoksa uygularız. Bu sayede, kaydırma hareketini yalnızca bunu tercih eden kullanıcılara sunuyoruz.

Sekme göstergesi

Bu animasyonun amacı, göstergeyi içeriğin durumuyla ilişkilendirmeye yardımcı olmaktır. Daha az hareket tercih eden kullanıcılar için border-bottom stillerini renk geçişli, hareketi sorun etmeyen kullanıcılar için ise kaydırmaya bağlı kayma ve renk geçişi animasyonu kullanmaya karar verdim.

Chromium Geliştirici Araçları'nda tercihi değiştirebilir ve 2 farklı geçiş stilini gösterebilirim. Bunu yaparken çok eğlendim.

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

Kullanıcı azaltılmış hareketi tercih ettiğinde .snap-indicator öğesini gizliyorum çünkü artık ihtiyacım yok. Ardından bunu border-block-end stilleri ve bir transition ile değiştiriyorum. Sekme etkileşiminde, etkin gezinme öğesinin yalnızca markanın alt çizgi vurgusuna sahip olmadığını, metin renginin de daha koyu olduğunu görebilirsiniz. Etkin öğe, daha yüksek metin rengi kontrastına ve parlak bir alt ışık vurgusuna sahiptir.

Yalnızca birkaç satır CSS ekleyerek kullanıcıların kendilerini özel hissetmelerini sağlayabilirsiniz (hareket tercihlerine saygı duyduğumuz için). Beğendim.

@scroll-timeline

Yukarıdaki bölümde, azaltılmış hareket çapraz geçiş stillerini nasıl işlediğimi gösterdim. Bu bölümde ise göstergeyi ve kaydırma alanını nasıl birbirine bağladığımı göstereceğim. Sırada eğlenceli ve deneysel bir içerik var. Umarım siz de benim kadar heyecanlısınızdır.

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

Öncelikle JavaScript'ten kullanıcının hareket tercihini kontrol ediyorum. Bu sorgunun sonucu false ise (yani kullanıcı daha az hareket tercih ediyorsa) kaydırmaya bağlı hareket efektlerinin hiçbirini çalıştırmayız.

if (motionOK) {
  // motion based animation code
}

Bu makalenin yazıldığı sırada @scroll-timeline için tarayıcı desteği yoktur. Yalnızca deneysel uygulamalar içeren taslak bir spesifikasyondur. Ancak bu demoda kullandığım bir polyfill'i var.

ScrollTimeline

Hem CSS hem de JavaScript ile kaydırma zaman çizelgeleri oluşturulabilir ancak animasyonda canlı öğe ölçümlerini kullanabilmek için JavaScript'i tercih ettim.

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

Bir öğenin başka bir öğenin kaydırma konumunu takip etmesini istiyorum. ScrollTimeline oluşturarak kaydırma bağlantısının sürücüsünü, yani scrollSource öğesini tanımlıyorum. Normalde web'deki animasyonlar genel bir zaman çerçevesi işaretine göre çalışır ancak bellekte özel bir sectionScrollTimeline ile tüm bunları değiştirebilirim.

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Animasyonun karelerine girmeden önce, kaydırmayı takip eden tabindicator öğesinin, bölümümüzün kaydırması olan özel bir zaman çizelgesine göre animasyonlu olacağını belirtmek önemlidir. Bu işlem bağlantıyı tamamlar ancak animasyonun uygulanacağı durum noktaları (anahtar kareler) eksiktir.

Dinamik animasyon kareleri

@scroll-timeline ile animasyon oluşturmak için tamamen bildirim temelli, çok güçlü bir CSS yöntemi var ancak benim seçtiğim animasyon çok dinamikti. auto genişliği arasında geçiş yapmanın bir yolu yoktur ve alt öğe uzunluğuna göre dinamik olarak bir dizi animasyon karesi oluşturmanın bir yolu yoktur.

Ancak JavaScript bu bilgileri nasıl alacağını bilir. Bu nedenle, alt öğeleri kendimiz iterasyona tabi tutar ve çalışma zamanında hesaplanan değerleri alırız:

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Her tabnavitem için offsetLeft konumunu parçalara ayırın ve bunu translateX değeri olarak kullanan bir dize döndürün. Bu işlem, animasyon için 4 dönüşüm anahtar karesi oluşturur. Aynı işlem genişlik için de yapılır. Her birine dinamik genişliğinin ne olduğu sorulur ve ardından bu değer, bir animasyon karesi değeri olarak kullanılır.

Yazı tiplerime ve tarayıcı tercihlerime göre örnek bir çıkış aşağıda verilmiştir:

TranslateX Animasyon Kareleri:

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

Genişlik Animasyon Kareleri:

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

Stratejiyi özetlemek gerekirse sekme göstergesi artık bölüm kaydırıcının kaydırarak tutturma konumuna bağlı olarak 4 kare boyunca animasyonlu olarak gösterilecek. Yakalama noktaları, animasyon karelerimiz arasında net bir ayrım oluşturur ve animasyonun senkronize hissini gerçekten artırır.

Etkin sekme ve etkin olmayan sekme, her ikisi için de başarılı kontrast puanlarını gösteren VisBug yer paylaşımlarıyla gösterilir.

Kullanıcı, etkileşimiyle animasyonu yönlendirir. Göstergenin genişliğinin ve konumunun bir bölümden diğerine değiştiğini görür ve kaydırma işlemini mükemmel bir şekilde takip eder.

Fark etmemiş olabilirsiniz ancak vurgulanan gezinme öğesi seçildiğinde renk geçişinin nasıl yapıldığıyla gurur duyuyorum.

Seçili olmayan açık gri, vurgulanan öğe daha fazla kontrasta sahip olduğunda daha da geride görünür. Metin rengini fareyle üzerine gelindiğinde ve seçildiğinde değiştirmek yaygın bir uygulamadır. Ancak bu rengi kaydırma sırasında alt çizgi göstergesiyle senkronize olarak değiştirmek, deneyimi bir üst seviyeye taşır.

Bunu nasıl yaptığımı aşağıda açıklıyorum:

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

Her sekme gezinme bağlantısının, alt çizgi göstergesiyle aynı kaydırma zaman çizelgesini izleyen bu yeni renk animasyonuna ihtiyacı vardır. Öncekiyle aynı zaman çizelgesini kullanıyorum. Görevi kaydırma sırasında bir işaret yaymak olduğundan bu işareti istediğimiz animasyon türünde kullanabiliriz. Daha önce yaptığım gibi döngüde 4 animasyon karesi oluşturup renkleri döndürüyorum.

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

var(--text-active-color) renkli kare, bağlantıyı vurgular. Diğer durumlarda standart metin rengi kullanılır. İç içe yerleştirilmiş döngü, dış döngü her gezinme öğesi ve iç döngü her gezinme öğesinin kişisel animasyon kareleri olduğundan bunu nispeten kolaylaştırır. Dış döngü öğesinin iç döngü öğesiyle aynı olup olmadığını kontrol ediyorum ve seçildiğinde bunu kullanıyorum.

Bunu yazarken çok eğlendim. Çok fazla.

Daha fazla JavaScript geliştirmesi

Burada gösterdiğim şeyin özünün JavaScript olmadan çalıştığını hatırlatmakta fayda var. Şimdi, JS mevcut olduğunda bunu nasıl geliştirebileceğimize bakalım.

Derin bağlantılar daha çok mobil cihazlarla ilgili bir terimdir ancak derin bağlantının amacına burada sekmelerle ulaşıldığını düşünüyorum. URL'yi doğrudan bir sekmenin içeriğiyle paylaşabilirsiniz. Tarayıcı, URL karma değerinde eşleşen kimliğe sayfada gezinme işlemi gerçekleştirir. Bu onload işleyicinin, platformlar genelinde etki yarattığını fark ettim.

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

Kaydırma sonu senkronizasyonu

Kullanıcılarımız her zaman tıklama yapmaz veya klavye kullanmaz. Bazen sadece serbestçe kaydırma yaparlar. Bölüm kaydırıcı kaydırmayı durdurduğunda, durduğu yerin üst gezinme çubuğunda eşleşmesi gerekir.

Kaydırma işleminin bitmesini şu şekilde bekliyorum: js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

Bölümler kaydırıldığında, varsa bölüm zaman aşımını temizleyin ve yeni bir zaman aşımı başlatın. Bölümler kaydırılmayı bıraktığında zaman aşımını temizlemeyin ve 100 ms sonra etkinleştirin. Bu etkinlik tetiklendiğinde, kullanıcının nerede durduğunu belirlemeye çalışan işlevi çağırın.

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

Kaydırarak tutturma işleminin gerçekleştiğini varsayarsak mevcut kaydırma konumunun kaydırma alanının genişliğine bölünmesi ondalık sayı değil, tam sayı sonucunu vermelidir. Ardından, bu hesaplanan dizin aracılığıyla önbelleğimizden bir gezinme öğesi almaya çalışıyorum ve bir şey bulursa eşleşmeyi etkin olarak ayarlanması için gönderiyorum.

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

Etkin sekme ayarlanırken önce mevcut etkin sekme temizlenir, ardından gelen gezinme öğesine etkin durum özelliği verilir. scrollIntoView() çağrısının CSS ile eğlenceli bir etkileşimi var. Bu etkileşimi incelemenizi öneririz.

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

Yatay kaydırarak tutturma yardımcı programı CSS'sinde, kullanıcı harekete toleranslıysa smooth kaydırma uygulayan bir medya sorgusunu iç içe yerleştirdik. JavaScript, öğeleri görünüme kaydırmak için serbestçe çağrı yapabilir ve CSS, kullanıcı deneyimini bildirimli olarak yönetebilir. Bazen oldukça keyifli bir eşleşme oluştururlar.

Sonuç

Benim nasıl yaptığımı öğrendiniz. Peki siz nasıl yapardınız? Bu da eğlenceli bir bileşen mimarisi oluşturur. En sevdiği çerçevede yuvalar içeren ilk sürümü kim yapacak? 🙂

Yaklaşımlarımızı çeşitlendirelim ve web'de geliştirme yapmanın tüm yollarını öğrenelim. Bir Glitch oluşturun, kendi versiyonunuzu bana tweetleyin, ben de aşağıdaki Topluluk remiksleri bölümüne ekleyeyim.

Topluluk remiksleri