CSS ve JS'de esnek paneller

Oct 31 2020

30 günlük Wes Bos JS mücadelesini takip ediyordum, bu yüzden HTML ve CSS çoğunlukla kopyala-yapıştır, JS hakkında geri bildirim almak istiyorum (çoğunlukla). Teşekkürler.

const panels = document.querySelectorAll('.panel');

panels.forEach(panel => panel.addEventListener('click', () => {
  const isOpen = panel.classList.contains('open');
  panels.forEach(panel => panel.classList.remove('open'));
  if(!isOpen) {
    panel.classList.add('open');
  }
}));

panels.forEach(panel => panel.addEventListener('transitionend', e => {
  if(e.propertyName.includes('flex')) {
    panels.forEach(panel => {
      if(panel.classList.contains('open')) {
        panel.classList.add('open-active');
      } else {
        panel.classList.remove('open-active');
      }
    });
  }
}));
html {
  box-sizing: border-box;
  background: #ffc600;
  font-family: 'helvetica neue';
  font-size: 20px;
  font-weight: 200;
}

body {
  margin: 0;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

.panels {
  min-height: 100vh;
  overflow: hidden;
  display: flex;
}

.panel {
  background: #6B0F9C;
  box-shadow: inset 0 0 0 5px rgba(255, 255, 255, 0.1);
  color: white;
  text-align: center;
  align-items: center;
  /* Safari transitionend event.propertyName === flex */
  /* Chrome + FF transitionend event.propertyName === flex-grow */
  transition:
    font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11),
    flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), background 0.2s;
  font-size: 20px;
  background-size: cover;
  background-position: center;

  flex: 1;
  display: flex;
  justify-content: center;
  flex-direction: column;
}

.panel1 {
  background-image: url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500);
}

.panel2 {
  background-image: url(https://source.unsplash.com/rFKUFzjPYiQ/1500x1500);
}

.panel3 {
  background-image: url(https://images.unsplash.com/photo-1465188162913-8fb5709d6d57?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&w=1500&h=1500&fit=crop&s=967e8a713a4e395260793fc8c802901d);
}

.panel4 {
  background-image: url(https://source.unsplash.com/ITjiVXcwVng/1500x1500);
}

.panel5 {
  background-image: url(https://source.unsplash.com/3MNzGlQM7qs/1500x1500);
}

/* Flex Children */
.panel>* {
  margin: 0;
  width: 100%;
  transition: transform 0.5s;
  flex: 1 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

.panel *:first-child {
  transform: translateY(-100%);
}

.panel *:last-child {
  transform: translateY(100%);
}

.panel.open-active *:first-child,
.panel.open-active *:last-child {
  transform: translateY(0);
}

.panel p {
  text-transform: uppercase;
  font-family: 'Amatic SC', cursive;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45);
  font-size: 2em;
}

.panel p:nth-child(2) {
  font-size: 4em;
}

.panel.open {
  font-size: 40px;
  flex: 5;
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Flex Panels 💪</title>
  <link href="https://fonts.googleapis.com/css?family=Amatic+SC" rel="stylesheet" type="text/css">
</head>
<body>

  <div class="panels">
    <div class="panel panel1">
      <p>Hey</p>
      <p>Let's</p>
      <p>Dance</p>
    </div>
    <div class="panel panel2">
      <p>Give</p>
      <p>Take</p>
      <p>Receive</p>
    </div>
    <div class="panel panel3">
      <p>Experience</p>
      <p>It</p>
      <p>Today</p>
    </div>
    <div class="panel panel4">
      <p>Give</p>
      <p>All</p>
      <p>You can</p>
    </div>
    <div class="panel panel5">
      <p>Life</p>
      <p>In</p>
      <p>Motion</p>
    </div>
  </div>

</body>
</html>

Yanıtlar

7 CertainPerformance Oct 31 2020 at 22:02

Bana oldukça iyi görünüyor. Sadece dikkate alınması gereken birkaç şey görebiliyorum:

Daha kesin propertyName kontrolü Siz var if(e.propertyName.includes('flex')) {çünkü Safari kullanıyor flexve diğerleri kullanıyor flex-grow. Eğer Are emin olduğunu flexalt dize diğer olası CSS geçişleri mevcut olmayacaktır? Emin olsanız bile, kodun okuyucuları emin olacak mı? Her ===iki olasılığa karşı bir teste geçeceğim ya da en azından kullanacağım startsWith(ki bu, .includesher iki olasılık da başladığı için buradan biraz daha uygundur flex).

Ayrıca geçiş etkinliği adı hakkındaki yorumu, CSS'nin yanı sıra JS'ye de taşıyabilirsiniz.

Kısa sınıfListesi ayarı Bir koşula bağlı olarak bir sınıf adı eklemek veya bir sınıf adını kaldırmak istediğinizde, sınıfın eklenip eklenmeyeceğini belirten ikinci bir bağımsız değişkenle bir sınıf adını if(...) classList.add(...) else(...) classList.removetek classList.togglebir bağımsız değişken halinde yoğunlaştırabilirsiniz . Sizin

if(panel.classList.contains('open')) {
  panel.classList.add('open-active');
} else {
  panel.classList.remove('open-active');
}

basitleştirir

const { classList } = panel;
classList.toggle('open-active', classList.contains('open'));

Tarayıcı uyumluluğu Bazı eski tarayıcılar 2. argümanı desteklemese de, bu nedenle ne tür tarayıcıları desteklemeniz gerektiğini düşünün. Yalnızca makul derecede güncel tarayıcıları desteklemek istiyorsanız, sorun değil. Akılda tutulması gereken bir diğer nokta da NodeList.prototype.forEach, sadece birkaç yıl önce, 2016 veya 2017 civarında IIRC tanıtıldı; startsWithES6'dan daha yeni olduğu gibi , ya bir polyfill kullanın ya da bunun yerine yineleyiciler ve Babel kullanın, örneğin:

for (const panel of panels) {
  // do stuff with panel

(IE'yi desteklemek istiyorsanız, kodunuzu ES5 sözdizimine dönüştürmek için yine de Babel kullanıyor olmalısınız)

Geri dönüş geçersiz mi? arayanı panels.forEach(panel => panel.addEventListeneraramanın değerini döndürür . Yana onun geri aramaları dönmek ne görünmüyor, bu bir şey yapmaz. Bu gerçek bir sorun değil, ancak bazıları geri arama void döndürürse kodun biraz daha anlamlı olacağını düşünebilir (hiçbir ifade veya örtük dönüş yok ). ( Burada TypeScript'in TSLint'inde açıklanmıştır )addEventListenerforEachforEachforEachreturn

Tıklanabilir paneller Paneller tıklanabilir cursor: pointerolduğundan, kullanıcı için tıklanmaları gerektiğini daha açık hale getirmek için varsayılan imleç yerine değiştirilebilir?

Seçicileri elemanlar arasındaki boşluk Ben değiştirmek istiyorum .panel>*için .panel > *- bu ayrı unsurlar boşluklarla ayrılmış zaman biraz daha kolay okunmasını sağlar.

Tekrarlayan paneller yerine

<div class="panel panel1">
</div>
<div class="panel panel2">
</div>
.panel1 {
  background-image: url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500);
}

.panel2 {
  background-image: url(https://source.unsplash.com/rFKUFzjPYiQ/1500x1500);
}

:nth-childBunun yerine, fazladan panel#sınıfları tamamen kaldırmanıza izin vererek kullanmayı düşünün .

.panel:nth-child(1) {
  background-image: url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500);
}

.panel:nth-child(2) {
  background-image: url(https://source.unsplash.com/rFKUFzjPYiQ/1500x1500);
}

const panels = document.querySelectorAll('.panel');

panels.forEach((panel) => {
  panel.addEventListener('click', () => {
    const isOpen = panel.classList.contains('open');
    panels.forEach(panel => panel.classList.remove('open'));
    if (!isOpen) {
      panel.classList.add('open');
    }
  });
});

panels.forEach((panel) => {
  panel.addEventListener('transitionend', e => {
    /* Safari transitionend event.propertyName === flex */
    /* Chrome + FF transitionend event.propertyName === flex-grow */
    if (e.propertyName === 'flex' || e.propertyName === 'flex-grow') {
      panels.forEach(panel => {
        const { classList } = panel;
        classList.toggle('open-active', classList.contains('open'));
      });
    }
  })
});
html {
  box-sizing: border-box;
  background: #ffc600;
  font-family: 'helvetica neue';
  font-size: 20px;
  font-weight: 200;
}

body {
  margin: 0;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

.panels {
  min-height: 100vh;
  overflow: hidden;
  display: flex;
}

.panel {
  background: #6B0F9C;
  box-shadow: inset 0 0 0 5px rgba(255, 255, 255, 0.1);
  color: white;
  text-align: center;
  align-items: center;
  /* Safari transitionend event.propertyName === flex */
  /* Chrome + FF transitionend event.propertyName === flex-grow */
  transition:
    font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11),
    flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), background 0.2s;
  font-size: 20px;
  background-size: cover;
  background-position: center;

  flex: 1;
  display: flex;
  justify-content: center;
  flex-direction: column;
  
  cursor: pointer;
}

.panel:nth-child(1) {
  background-image: url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500);
}

.panel:nth-child(2) {
  background-image: url(https://source.unsplash.com/rFKUFzjPYiQ/1500x1500);
}

.panel:nth-child(3) {
  background-image: url(https://images.unsplash.com/photo-1465188162913-8fb5709d6d57?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&w=1500&h=1500&fit=crop&s=967e8a713a4e395260793fc8c802901d);
}

.panel:nth-child(4) {
  background-image: url(https://source.unsplash.com/ITjiVXcwVng/1500x1500);
}

.panel:nth-child(5) {
  background-image: url(https://source.unsplash.com/3MNzGlQM7qs/1500x1500);
}

/* Flex Children */
.panel > * {
  margin: 0;
  width: 100%;
  transition: transform 0.5s;
  flex: 1 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

.panel *:first-child {
  transform: translateY(-100%);
}

.panel *:last-child {
  transform: translateY(100%);
}

.panel.open-active *:first-child,
.panel.open-active *:last-child {
  transform: translateY(0);
}

.panel p {
  text-transform: uppercase;
  font-family: 'Amatic SC', cursive;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45);
  font-size: 2em;
}

.panel p:nth-child(2) {
  font-size: 4em;
}

.panel.open {
  font-size: 40px;
  flex: 5;
}
<link href="https://fonts.googleapis.com/css?family=Amatic+SC" rel="stylesheet" type="text/css">
<div class="panels">
  <div class="panel">
    <p>Hey</p>
    <p>Let's</p>
    <p>Dance</p>
  </div>
  <div class="panel">
    <p>Give</p>
    <p>Take</p>
    <p>Receive</p>
  </div>
  <div class="panel">
    <p>Experience</p>
    <p>It</p>
    <p>Today</p>
  </div>
  <div class="panel">
    <p>Give</p>
    <p>All</p>
    <p>You can</p>
  </div>
  <div class="panel">
    <p>Life</p>
    <p>In</p>
    <p>Motion</p>
  </div>
</div>

4 SᴀᴍOnᴇᴌᴀ Nov 01 2020 at 05:50

gözden geçirmek

CertainPerformance'ın cevabına katılıyorum: kod oldukça iyi görünüyor. Girinti tutarlı görünüyor, değişken isimleri uygun ve satırlar iyi sonlandırılmış. Okunabilirlik harika.

Verimsiz döngüler

Olay işleyicilerindeki kod, tüm panel öğelerinin üzerinde döngü oluşturur, ancak en fazla iki öğenin sınıf listelerinde güncellemeler olabilir. Döngüleri ortadan kaldırmanın yolları hakkında aşağıdaki öneriye bakın.

Aşırı CSS kuralları

Aynı kural üzerinde de belirtildiği için stil font-size: 20px;gerekli değildir .panel, ayrıca htmldaha spesifik seçiciler tarafından geçersiz kılınır.

Öneri

Olay temsilcisini kullanarak döngüleri kaldırın

Her panel öğesine olay dinleyicileri eklemek yerine, konteyner öğesine olay dinleyicileri eklenebilir. Bu, olay işleyicilerini olay hedefine bakacak şekilde değiştirmeyi ve hedefin bir panel veya panelin alt öğesi ile eşleşip eşleşmediğini belirlemeyi gerektirir - bu yöntemle yapılabilir .closest(). Ve sınıf adı olan canlı bir HTMLCollectionopen kullanılarak bir kez alınabilir document.getElementsByClassName('open');. Ardından, tıklama işleyicisi çağrıldığında bu sınıfa sahip herhangi bir öğe varsa, sınıf adı kaldırılabilir.

Bu, olay işleyicilerini üzerlerine kaydetmeye gerek kalmadan panellerin eklenmesine ve kaldırılmasına izin verir. Böyle küçük bir sayfada dikkate değer bir fark yaratmasa da, bir döngüden kaçınılabilecek yerleri düşünmek akıllıca olacaktır.

Aşağıdaki kod parçacığında, döngüler ortadan kaldırılmıştır.

const panelsContainer = document.querySelector('.panels');
const openPanels = document.getElementsByClassName('open');
const openActivePanels = document.getElementsByClassName('open-active');

panelsContainer.addEventListener('click', e => {
  const panel = e.target.closest('.panel');
  if (!panel) {
    return;
  }
  const isOpen = panel.classList.contains('open');
  if (openPanels.length) {
    openPanels[0].classList.remove('open');
  }
  panel.classList.toggle('open', !isOpen);
});
panelsContainer.addEventListener('transitionend', e => {
  /* Safari transitionend event.propertyName === flex */
  /* Chrome + FF transitionend event.propertyName === flex-grow */
  if (e.propertyName === 'flex' || e.propertyName === 'flex-grow') {
    if (openActivePanels.length) {
      openActivePanels[0].classList.toggle('open-active', openActivePanels[0].classList.contains('open'))
    }
    if (openPanels.length) {
      openPanels[0].classList.add('open-active')
    }
  }
})
html {
  box-sizing: border-box;
  background: #ffc600;
  font-family: 'helvetica neue';
  font-size: 20px;
  font-weight: 200;
}

body {
  margin: 0;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

.panels {
  min-height: 100vh;
  overflow: hidden;
  display: flex;
}

.panel {
  background: #6B0F9C;
  box-shadow: inset 0 0 0 5px rgba(255, 255, 255, 0.1);
  color: white;
  text-align: center;
  align-items: center;
  /* Safari transitionend event.propertyName === flex */
  /* Chrome + FF transitionend event.propertyName === flex-grow */
  transition: font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), background 0.2s;
  
  background-size: cover;
  background-position: center;
  flex: 1;
  display: flex;
  justify-content: center;
  flex-direction: column;
}

.panel1 {
  background-image: url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500);
}

.panel2 {
  background-image: url(https://source.unsplash.com/rFKUFzjPYiQ/1500x1500);
}

.panel3 {
  background-image: url(https://images.unsplash.com/photo-1465188162913-8fb5709d6d57?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&w=1500&h=1500&fit=crop&s=967e8a713a4e395260793fc8c802901d);
}

.panel4 {
  background-image: url(https://source.unsplash.com/ITjiVXcwVng/1500x1500);
}

.panel5 {
  background-image: url(https://source.unsplash.com/3MNzGlQM7qs/1500x1500);
}


/* Flex Children */

.panel>* {
  margin: 0;
  width: 100%;
  transition: transform 0.5s;
  flex: 1 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

.panel *:first-child {
  transform: translateY(-100%);
}

.panel *:last-child {
  transform: translateY(100%);
}

.panel.open-active *:first-child,
.panel.open-active *:last-child {
  transform: translateY(0);
}

.panel p {
  text-transform: uppercase;
  font-family: 'Amatic SC', cursive;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45);
  font-size: 2em;
}

.panel p:nth-child(2) {
  font-size: 4em;
}

.panel.open {
  font-size: 40px;
  flex: 5;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Flex Panels 💪</title>
  <link href="https://fonts.googleapis.com/css?family=Amatic+SC" rel="stylesheet" type="text/css">
</head>

<body>

  <div class="panels">
    <div class="panel panel1">
      <p>Hey</p>
      <p>Let's</p>
      <p>Dance</p>
    </div>
    <div class="panel panel2">
      <p>Give</p>
      <p>Take</p>
      <p>Receive</p>
    </div>
    <div class="panel panel3">
      <p>Experience</p>
      <p>It</p>
      <p>Today</p>
    </div>
    <div class="panel panel4">
      <p>Give</p>
      <p>All</p>
      <p>You can</p>
    </div>
    <div class="panel panel5">
      <p>Life</p>
      <p>In</p>
      <p>Motion</p>
    </div>
  </div>

</body>

</html>