Kendi Genişleyen ve Daralan İçerik Panellerinizi Yapın

Yayınlanan: 2022-03-10
Hızlı özet ↬ UI/UX'te, tekrar tekrar ihtiyaç duyulan yaygın bir kalıp, basit bir animasyonlu açma ve kapama paneli veya 'çekmece'dir. Bunları yapmak için bir kütüphaneye ihtiyacınız yok. Bazı temel HTML/CSS ve JavaScript ile bunu kendimiz yapmayı öğreneceğiz.

Bunları şimdiye kadar bir 'açma ve kapama paneli' olarak adlandırdık, ancak bunlar aynı zamanda genişleme panelleri veya daha basit bir ifadeyle genişleyen paneller olarak da tanımlanmaktadır.

Tam olarak neden bahsettiğimizi netleştirmek için CodePen'deki bu örneğe gidin:

CodePen'de Ben Frain'den kolay göster/gizle çekmecesi (Çoklu).

CodePen'de Ben Frain'den kolay göster/gizle çekmecesi (Çoklu).

Bu kısa eğitimde inşa edeceğimiz şey budur.

İşlevsellik açısından, aradığımız animasyonlu açma ve kapamaya ulaşmanın birkaç yolu vardır. Her yaklaşımın kendi faydaları ve takasları vardır. Bu yazıda 'git' yöntemimin detaylarını detaylı olarak paylaşacağım. Önce olası yaklaşımları ele alalım.

Yaklaşımlar

Bu tekniklerin varyasyonları vardır, ancak genel olarak konuşursak, yaklaşımlar üç kategoriden birine girer:

  1. İçeriğin height veya max-height yüksekliğini canlandırın/geçiş yapın.
  2. Öğeleri yeni bir konuma taşımak için transform: translateY kullanın, bu da bir panelin kapandığı yanılsaması verir ve ardından dönüşüm, öğeler bitiş konumlarındayken dönüşüm tamamlandığında DOM'yi yeniden oluşturur.
  3. 1 veya 2'nin bazı kombinasyonlarını/varyasyonlarını yapan bir kitaplık kullanın!
Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

Her Yaklaşımın Hususları

Performans açısından bakıldığında, bir dönüşüm kullanmak, yükseklik/maksimum yüksekliği canlandırmak veya geçiş yapmaktan daha etkilidir. Bir dönüşümle, hareketli öğeler rasterleştirilir ve GPU tarafından kaydırılır. Bu, bir GPU için ucuz ve kolay bir işlemdir, bu nedenle performans çok daha iyi olma eğilimindedir.

Bir dönüştürme yaklaşımı kullanırken temel adımlar şunlardır:

  1. Daraltılacak içeriğin yüksekliğini alın.
  2. İçeriği ve sonraki her şeyi, transform: translateY(Xpx) kullanarak daraltılacak içeriğin yüksekliğine göre taşıyın. Hoş bir görsel efekt vermek için dönüşümü tercih edilen geçişle çalıştırın.
  3. transitionend olayı olayını dinlemek için JavaScript kullanın. Ateşlendiğinde, şunu display: none içeriği ve dönüşümü kaldırın ve her şey doğru yerde olmalıdır.

Kulağa çok kötü gelmiyor, değil mi?

Bununla birlikte, bu teknikle ilgili bir takım hususlar vardır, bu yüzden performans kesinlikle çok önemli olmadıkça, sıradan uygulamalar için bundan kaçınma eğilimindeyim.

Örneğin, transform: translateY yaklaşımıyla, öğelerin z-index dikkate almanız gerekir. Varsayılan olarak, dönüşen öğeler DOM'daki tetikleyici öğeden sonradır ve bu nedenle, çevrildiğinde onlardan önceki öğelerin üstünde görünür.

Ayrıca DOM'da daraltmak istediğiniz içerikten sonra kaç tane şeyin göründüğünü de göz önünde bulundurmalısınız. Mizanpajınızda büyük bir delik istemiyorsanız, taşımak istediğiniz her şeyi bir kapsayıcı öğesinde sarmak için JavaScript'i kullanmayı ve yalnızca onu taşımayı daha kolay bulabilirsiniz. Yönetilebilir, ancak daha fazla karmaşıklık getirdik! Ancak bu, oyuncuları İçeri/Dışarıda yukarı ve aşağı hareket ettirirken izlediğim yaklaşım türüdür . Bunun nasıl yapıldığını buradan görebilirsiniz.

Daha gündelik ihtiyaçlar için, içeriğin max-height değiştirme eğilimindeyim. Bu yaklaşım, bir dönüşüm kadar iyi performans göstermez. Tarayıcının geçiş boyunca çöken öğenin yüksekliğinin arasını doldurmasının nedeni; bu, ana bilgisayar için ucuz olmayan birçok yerleşim hesaplamasına neden olur.

Ancak, bu yaklaşım basitlik açısından kazanır. Yukarıda bahsedilen hesaplama isabetinden muzdarip olmanın getirisi, DOM yeniden akışının her şeyin konumu ve geometrisi ile ilgilenmesidir. Yazacak çok az hesaplamamız var ve bunu başarmak için gereken JavaScript nispeten basit.

Odadaki Fil: Ayrıntılar ve Özet Öğeler

HTML öğeleri hakkında derin bilgiye sahip olanlar, bu soruna details ve summary öğeler biçiminde yerel bir HTML çözümü olduğunu bileceklerdir. İşte bazı örnek işaretleme:

 <details> <summary>Click to open/close</summary> Here is the content that is revealed when clicking the summary... </details>

Varsayılan olarak, tarayıcılar özet öğesinin yanında küçük bir açıklama üçgeni sağlar; özeti tıklayın ve özetin altındaki içerik ortaya çıkıyor.

Harika, hey? Ayrıntılar, JavaScript'te toggle olayını bile destekler, böylece açık veya kapalı olmasına bağlı olarak farklı şeyler gerçekleştirmek için bu tür şeyler yapabilirsiniz (bu tür bir JavaScript ifadesi tuhaf görünüyorsa endişelenmeyin; buna daha fazla değineceğiz). kısaca detay):

 details.addEventListener("toggle", () => { details.open ? thisCoolThing() : thisOtherThing(); })

Tamam, heyecanını orada keseceğim. Ayrıntılar ve özet öğeler canlandırılmıyor. Varsayılan olarak değil ve şu anda ek CSS ve JavaScript ile animasyon/geçiş yapmalarını ve kapatmalarını sağlamak mümkün değil.

Aksini biliyorsan, haksız çıkmayı çok isterim.

Ne yazık ki, bir açma ve kapama estetiğine ihtiyacımız olduğu için kolları sıvamamız ve elimizdeki diğer araçlarla elimizden gelenin en iyi ve en erişilebilir işini yapmamız gerekecek.

Pekala, iç karartıcı haberler aradan çıktığına göre, hadi bu şeyi gerçekleştirmeye başlayalım.

İşaretleme Deseni

Temel işaretleme şöyle görünecek:

 <div class="container"> <button type="button" class="trigger">Show/Hide content</button> <div class="content"> All the content here </div> </div>

Genişleticiyi sarmak için bir dış kabımız var ve ilk öğe, eylemi tetikleyen düğmedir. Düğmedeki type niteliğine dikkat ettiniz mi? Her zaman varsayılan olarak bir form içindeki bir düğmenin bir gönderme gerçekleştireceğini ekliyorum. Formunuzun neden çalışmadığını ve formunuzda düğmeler olduğunu merak ederek birkaç saatinizi boşa harcıyorsanız; type niteliğini kontrol ettiğinizden emin olun!

Düğmeden sonraki öğe, içerik çekmecesinin kendisidir; gizlemek ve göstermek istediğiniz her şey.

Bir şeyleri hayata geçirmek için CSS özel özelliklerini, CSS geçişlerini ve biraz JavaScript'i kullanacağız.

Temel Mantık

Temel mantık şudur:

  1. Sayfanın yüklenmesine izin verin, içeriğin yüksekliğini ölçün.
  2. İçeriğin kapsayıcı üzerindeki yüksekliğini bir CSS Özel Özelliğinin değeri olarak ayarlayın.
  3. Bir aria-hidden: "true" özniteliği ekleyerek içeriği hemen gizleyin. aria-hidden kullanmak, yardımcı teknolojinin içeriğin de gizli olduğunu bilmesini sağlar.
  4. İçerik sınıfının max-height özel özelliğin değeri olacak şekilde CSS'yi bağlayın.
  5. Tetik düğmemize basmak, aria-hidden özelliğini true'dan false'a değiştirir, bu da içeriğin max-height 0 ile özel özellikte ayarlanan yükseklik arasında değiştirir. Bu özellik üzerinde bir geçiş, görsel yetenek sağlar - zevke göre ayarlayın!

Not: max-height: auto , içeriğin yüksekliğine eşitse, bu, bir sınıf veya özniteliği değiştirmenin basit bir durumu olacaktır. Ne yazık ki öyle değil. Git ve bunu W3C'ye buradan bağır.

Bu yaklaşımın kodda nasıl ortaya çıktığına bir göz atalım. Numaralandırılmış yorumlar, kodda yukarıdan eşdeğer mantık adımlarını gösterir.

İşte JavaScript:

 // Get the containing element const container = document.querySelector(".container"); // Get content const content = document.querySelector(".content"); // 1. Get height of content you want to show/hide const heightOfContent = content.getBoundingClientRect().height; // Get the trigger element const btn = document.querySelector(".trigger"); // 2. Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { document.documentElement.classList.add("height-is-set"); 3. content.setAttribute("aria-hidden", "true"); }, 0); btn.addEventListener("click", function(e) { container.setAttribute("data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true"); // 5. Toggle aria-hidden content.setAttribute("aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true"); })

CSS:

 .content { transition: max-height 0.2s; overflow: hidden; } .content[aria-hidden="true"] { max-height: 0; } // 4. Set height to value of custom property .content[aria-hidden="false"] { max-height: var(--containerHeight, 1000px); }

Dikkat Edilecek Noktalar

Peki ya çoklu çekmeceler?

Bir sayfada çok sayıda aç ve gizle çekmeceniz olduğunda, büyük olasılıkla farklı boyutlarda olacağından, hepsini gözden geçirmeniz gerekir.

Bununla başa çıkmak için tüm kapsayıcıları almak için bir querySelectorAll ve ardından bir forEach içindeki her içerik için özel değişkenler ayarınızı yeniden çalıştırmamız gerekecek.

Bu setTimeout

Kapsayıcıyı gizlenecek şekilde ayarlamadan önce 0 süreli bir setTimeout var. Bu muhtemelen gereksizdir, ancak sayfanın önce işlenmesini sağlamak için bir 'kemer ve parantez' yaklaşımı olarak kullanıyorum, böylece içeriğin yükseklikleri okunabilir.

Bunu yalnızca sayfa hazır olduğunda ateşleyin

Devam eden başka işleriniz varsa, çekmece kodunuzu sayfa yüklendiğinde başlatılan bir işleve sarmayı seçebilirsiniz. Örneğin, çekmece işlevinin initDrawers adlı bir işleve sarıldığını varsayalım, bunu yapabiliriz:

 window.addEventListener("load", initDrawers);

Aslında onu da birazdan ekleyeceğiz.

Kapsayıcıdaki ek data-* öznitelikleri

Dış kapta da değiştirilen bir veri özniteliği vardır. Bu, çekmece açılırken/kapanırken tetik veya kap ile değişmesi gereken herhangi bir şey olması durumunda eklenir. Örneğin, belki bir şeyin rengini değiştirmek veya bir simgeyi ortaya çıkarmak veya değiştirmek istiyoruz.

Özel mülkte varsayılan değer

1000px özel özellikte varsayılan bir değer ayarlanmıştır. Bu, değerin içindeki virgülden sonraki kısımdır: var(--containerHeight, 1000px) . Bu, --containerHeight bir şekilde bozulursa, yine de düzgün bir geçişe sahip olmanız gerektiği anlamına gelir. Bunu, kullanım durumunuza uygun olana açıkça ayarlayabilirsiniz.

Neden Sadece 100000px Varsayılan Değerini Kullanmıyorsunuz?

max-height: auto geçiş yapmadığı göz önüne alındığında, neden ihtiyaç duyacağınızdan daha yüksek bir değere sahip bir yüksekliği seçmediğinizi merak ediyor olabilirsiniz. Örneğin, 10000000 piksel?

Bu yaklaşımla ilgili sorun, her zaman o yükseklikten geçiş yapacak olmasıdır. Geçiş süreniz 1 saniyeye ayarlanmışsa, geçiş bir saniyede 10000000 piksel 'seyahat eder'. İçeriğiniz yalnızca 50 piksel yüksekliğindeyse, oldukça hızlı bir açılış/kapanış efekti elde edeceksiniz!

Geçişler için üçlü operatör

Nitelikleri değiştirmek için birkaç kez üçlü operatör kullandık. Bazı insanlar onlardan nefret ediyor ama ben ve diğerleri onları seviyorum. İlk başta biraz garip ve biraz 'kod golf' gibi görünebilirler, ancak sözdizimine alıştığınızda, standart bir if/else'den daha basit bir okuma olduklarını düşünüyorum.

Başlatılmamış olanlar için, üçlü bir operatör, if/else'nin yoğunlaştırılmış bir şeklidir. Kontrol edilecek şey önce, sonra ? kontrol doğruysa neyin yürütüleceğini ayırır ve ardından kontrol yanlışsa neyin çalışması gerektiğini ayırt etmek için : .

 isThisTrue ? doYesCode() : doNoCode();

Öznitelik geçişlerimiz, bir özniteliğin "true" olarak ayarlanıp ayarlanmadığını kontrol ederek çalışır ve öyleyse, "false" olarak ayarlayın, aksi takdirde "true" olarak ayarlayın.

Sayfa yeniden boyutlandırıldığında ne olur?

Bir kullanıcı tarayıcı penceresini yeniden boyutlandırırsa, içeriğimizin yüksekliklerinin değişme olasılığı yüksektir. Bu nedenle, bu senaryoda kapların yüksekliğini ayarlamayı yeniden çalıştırmak isteyebilirsiniz. Şimdi bu tür olasılıkları düşünüyoruz, bazı şeyleri yeniden gözden geçirmek için iyi bir zaman gibi görünüyor.

Yükseklikleri ayarlamak için bir işlev ve etkileşimlerle başa çıkmak için başka bir işlev yapabiliriz. Ardından pencereye iki dinleyici ekleyin; biri yukarıda belirtildiği gibi belge yüklendiğinde, diğeri yeniden boyutlandırma olayını dinlemek için.

Biraz fazladan A11Y

aria-expanded , aria-controls ve aria-labelledby özniteliklerinden yararlanarak erişilebilirlik için biraz fazladan değerlendirme eklemek mümkündür. Bu, çekmeceler açıldığında/genişletildiğinde destekli teknolojiye daha iyi bir gösterge verecektir. Düğme işaretlememize aria-controls="IDofcontent" ile birlikte aria aria-expanded="false" ekleriz; burada IDofcontent , içerik kabına eklediğimiz kimliğin değeridir.

Ardından, JavaScript'te tıklandığında aria-expanded özniteliği değiştirmek için başka bir üçlü operatör kullanırız.

Hep birlikte

Sayfa yükleme, çoklu çekmeceler, ekstra A11Y çalışması ve yeniden boyutlandırma olaylarını işleme ile JavaScript kodumuz şöyle görünür:

 var containers; function initDrawers() { // Get the containing elements containers = document.querySelectorAll(".container"); setHeights(); wireUpTriggers(); window.addEventListener("resize", setHeights); } window.addEventListener("load", initDrawers); function setHeights() { containers.forEach(container => { // Get content let content = container.querySelector(".content"); content.removeAttribute("aria-hidden"); // Height of content to show/hide let heightOfContent = content.getBoundingClientRect().height; // Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { container.classList.add("height-is-set"); content.setAttribute("aria-hidden", "true"); }, 0); }); } function wireUpTriggers() { containers.forEach(container => { // Get each trigger element let btn = container.querySelector(".trigger"); // Get content let content = container.querySelector(".content"); btn.addEventListener("click", () => { btn.setAttribute("aria-expanded", btn.getAttribute("aria-expanded") === "false" ? "true" : "false"); container.setAttribute( "data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true" ); content.setAttribute( "aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true" ); }); }); }

Bununla CodePen'de de oynayabilirsiniz:

CodePen'de Ben Frain'den kolay göster/gizle çekmecesi (Çoklu).

CodePen'de Ben Frain'den kolay göster/gizle çekmecesi (Çoklu).

Özet

Giderek daha fazla durum için daha fazla rafine etme ve yiyecek sağlama bir süre daha devam etmek mümkündür, ancak içeriğiniz için güvenilir bir açma ve kapama çekmecesi oluşturmanın temel mekaniği artık elinizin altında olmalıdır. Umarım, bazı tehlikelerin de farkındasınızdır. details öğesi canlandırılamaz, max-height: auto umduğunuz şeyi yapmaz, güvenilir bir şekilde büyük bir maksimum yükseklik değeri ekleyemez ve tüm içerik panellerinin beklendiği gibi açılmasını bekleyemezsiniz.

Buradaki yaklaşımımızı tekrarlamak için: kabı ölçün, yüksekliğini bir CSS özel özelliği olarak saklayın, içeriği gizleyin ve ardından max-height 0 ile özel özellikte sakladığınız yükseklik arasında geçiş yapmak için basit bir geçiş kullanın.

Mutlak en iyi performans gösteren yöntem olmayabilir, ancak çoğu durumda tamamen yeterli olduğunu ve uygulanmasının nispeten basit olmasından fayda sağladığını gördüm.