Paneles flexibles en CSS y JS
Estaba siguiendo el desafío de 30 días de Wes Bos JS, por lo que HTML y CSS son en su mayoría para copiar y pegar, me gustaría recibir comentarios sobre JS (principalmente). Gracias.
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>
Respuestas
Se ve bastante bien para mí. Solo puedo ver algunas cosas a considerar:
Verificación de propertyName más precisa que tiene if(e.propertyName.includes('flex')) {porque Safari usa flexy otros usan flex-grow. ¿Está seguro de que la flexsubcadena no estará presente en ninguna otra posible transición de CSS? Incluso si está seguro, ¿estarán seguros los lectores del código? Cambiaría a una ===prueba contra ambas posibilidades, o al menos usaría startsWith(que es un poco más apropiado que .includesaquí, ya que ambas posibilidades comienzan con flex).
También puede mover el comentario sobre el nombre del evento de transición al JS y al CSS.
Configuración concisa de classList Cuando desee agregar un nombre de clase o eliminar un nombre de clase, según una condición, puede condensar un if(...) classList.add(...) else(...) classList.removeen uno classList.togglecon un segundo argumento que indica si agregar o eliminar la clase. Tu
if(panel.classList.contains('open')) {
panel.classList.add('open-active');
} else {
panel.classList.remove('open-active');
}
simplifica a
const { classList } = panel;
classList.toggle('open-active', classList.contains('open'));
Compatibilidad del navegador Sin embargo, algunos navegadores antiguos no son compatibles con el segundo argumento, así que considere qué tipo de navegadores necesita admitir. Si solo desea admitir navegadores razonablemente actualizados, está bien. Otra cosa a tener en cuenta es que NodeList.prototype.forEachse introdujo hace solo unos años, alrededor de 2016 o 2017 IIRC; como startsWith, es más nuevo que ES6, así que use un polyfill o use iteradores y Babel en su lugar, por ejemplo:
for (const panel of panels) {
// do stuff with panel
(si desea admitir IE, debe usar Babel de todos modos, para transpilar su código a la sintaxis de ES5)
¿Retorno nulo? panels.forEach(panel => panel.addEventListenerdevuelve el valor de la llamada addEventListeneral llamador de forEach. Dado forEachque no mira lo que devuelven sus devoluciones de llamada, esto no hace nada. No es un problema real, pero algunos podrían considerar que el código tiene un poco más de sentido si la forEachdevolución de llamada devolvió nulo (sin returndeclaración o devolución implícita). (Descrito en TSLint de TypeScript aquí )
Paneles en los que se puede hacer clic Dado que se puede hacer clic en los paneles, ¿tal vez cambiar del cursor predeterminado a cursor: pointerpara que sea más obvio para el usuario que se debe hacer clic en ellos?
El espacio entre los elementos de los selectores que cambiaría .panel>*a .panel > *- que hace que sea un poco más fácil de leer cuando los elementos separados están separados por espacios.
Paneles repetitivos en lugar de
<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);
}
Considere usar en su :nth-childlugar, lo que le permite eliminar las panel#clases adicionales por completo.
.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>
revisión
Estoy de acuerdo con la respuesta de CertainPerformance: el código se ve bastante bien. La sangría parece consistente, los nombres de las variables son apropiados y las líneas están bien terminadas. La legibilidad es excelente.
Bucles ineficientes
El código de los controladores de eventos recorre todos los elementos del panel, aunque como máximo solo dos elementos tendrían actualizaciones en su lista de clases. Vea la sugerencia a continuación sobre formas de eliminar los bucles.
Exceso de reglas CSS
El estilo font-size: 20px;no es necesario .panelya que también se especifica la misma regla html, además de que se reemplaza por selectores más específicos.
Sugerencia
Eliminar bucles mediante la delegación de eventos
En lugar de agregar detectores de eventos a cada elemento del panel, se pueden agregar detectores de eventos al elemento contenedor. Esto requeriría cambiar los controladores de eventos para mirar el objetivo del evento y determinar si el objetivo coincidía con un panel o un elemento secundario de un panel; se puede hacer con el .closest()método. Y una colección de elementos HTMLC en vivo con el nombre de la clase opense puede recuperar una vez usando document.getElementsByClassName('open');. Luego, si algún elemento tiene esa clase cuando se llama al controlador de clic, se puede eliminar el nombre de la clase.
Esto permitiría agregar y eliminar paneles sin necesidad de registrar los controladores de eventos en ellos. Si bien es posible que no haga una diferencia notable en una página pequeña como esta, es aconsejable considerar lugares donde se puede evitar un bucle.
En el fragmento de código siguiente, se eliminaron los bucles.
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>