CSS 및 JS의 Flex 패널

Oct 31 2020

Wes Bos JS 30 일 챌린지를 따르고 있었기 때문에 HTML과 CSS는 대부분 복사-붙여 넣기이므로 JS에 대한 피드백을 받고 싶습니다 (대부분). 감사.

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>

답변

7 CertainPerformance Oct 31 2020 at 22:02

나에게 꽤 좋아 보인다. 고려해야 할 몇 가지 사항 만 볼 수 있습니다.

더 정확한 propertyName 형식 체크 당신은이 if(e.propertyName.includes('flex')) {사파리 사용하기 때문에 flex다른 사람이 사용 flex-grow. 당신은 확실히 것을 flex문자열이 다른 가능한 CSS 전환에 존재하지 않을 것인가? 당신이 확신하더라도, 코드 독자들은 확신 할 것인가? 나는 ===두 가지 가능성 에 대한 테스트로 변경 하거나 적어도 사용합니다 startsWith( .includes두 가능성이 모두로 시작하기 때문에 여기 보다 조금 더 적절합니다 flex).

전환 이벤트 이름에 대한 주석을 CSS뿐만 아니라 JS로 이동할 수도 있습니다.

간결한 classList 설정 조건에 따라 클래스 이름을 추가하거나 클래스 이름을 제거하려는 경우 클래스 추가 또는 제거 여부를 나타내는 두 번째 인수를 사용하여을 if(...) classList.add(...) else(...) classList.remove단일로 압축 할 수 있습니다 classList.toggle. 너의

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

단순화

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

브라우저 호환성 그러나 일부 오래된 브라우저는 두 번째 인수를 지원하지 않으므로 지원해야하는 브라우저의 종류를 고려하십시오. 합리적으로 최신 브라우저 만 지원하고 싶다면 괜찮습니다. 명심해야 할 또 다른 사항 NodeList.prototype.forEach은 몇 년 전인 2016 년 또는 2017 년 IIRC에 도입되었습니다. 같은 startsWith, 그렇게 하나 대신 polyfill 또는 사용 반복자와 바벨을 사용 ES6보다 최신의, 예를 들면 :

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

(IE를 지원하려면 어쨌든 Babel을 사용하여 코드를 ES5 구문으로 변환해야합니다)

공허 반환? 호출자에게 panels.forEach(panel => panel.addEventListener호출 한 값을 반환합니다 . 콜백이 반환하는 내용을 보지 않기 때문에 아무 작업도 수행하지 않습니다. 실제 문제는 아니지만 콜백이 void를 반환 하면 코드를 좀 더 이해하기 위해 코드를 고려할 수 있습니다 ( 문 또는 암시 적 반환이 전혀 없음). ( 여기 에 TypeScript의 TSLint에 설명되어 있음 )addEventListenerforEachforEachforEachreturn

클릭 가능한 패널 패널은 클릭 cursor: pointer할 수 있으므로 기본 커서에서 로 변경 하여 사용자가 클릭해야 함을 더 명확하게 할 수 있습니까?

변경 .panel>*하려는 선택기의 요소 간 공백.panel > * -별도의 요소가 공백으로 구분 될 때 좀 더 쉽게 읽을 수 있습니다.

반복 패널 보다는

<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-child대신 사용 을 고려하여 추가 panel#클래스를 완전히 제거 할 수 있습니다 .

.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

리뷰

나는 특정 성능의 대답에 동의한다 : 코드가 꽤 좋아 보인다. 들여 쓰기가 일관되고 변수 이름이 적절하며 줄이 잘 종료됩니다. 가독성이 좋습니다.

비효율적 인 루프

이벤트 핸들러의 코드는 모든 패널 요소에 대해 반복되지만 최대 2 개의 요소 만 해당 클래스 목록을 업데이트합니다. 루프를 제거하는 방법에 대한 아래 제안을 참조하십시오.

초과 CSS 규칙

동일한 규칙이에 지정 font-size: 20px;되어 .panel있기 때문에 스타일 이 필요하지 않으며 html더 구체적인 선택자에 의해 재정의됩니다.

암시

이벤트 위임 을 사용하여 루프 제거

각 패널 요소에 이벤트 리스너를 추가하는 대신 컨테이너 요소에 이벤트 리스너를 추가 할 수 있습니다. 이를 위해서는 이벤트 대상을 살펴보고 대상이 패널 또는 패널의 자식과 일치하는지 확인하기 위해 이벤트 처리기를 변경해야합니다 .closest(). 메서드를 사용하여 수행 할 수 있습니다 . 클래스 이름 이있는 요소 의 라이브 HTMLCollection 은 . 그런 다음 클릭 핸들러가 호출 될 때 해당 클래스가있는 요소가 있으면 클래스 이름을 제거 할 수 있습니다.opendocument.getElementsByClassName('open');

이를 통해 이벤트 핸들러를 등록 할 필요없이 패널을 추가하고 제거 할 수 있습니다. 이와 같은 작은 페이지에서는 눈에 띄는 차이를 만들지 못할 수 있지만 루프를 피할 수있는 위치를 고려하는 것이 좋습니다.

아래 코드 스 니펫에서는 루프가 제거되었습니다.

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>