Pannelli flessibili in CSS e JS
Stavo seguendo la sfida di 30 giorni di Wes Bos JS, quindi HTML e CSS sono per lo più copia-incolla, vorrei un feedback su JS (principalmente). Grazie.
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>
Risposte
Mi sembra abbastanza buono. Posso solo vedere alcune cose da considerare:
Controllo del nome di proprietà più preciso Hai if(e.propertyName.includes('flex')) {perché Safari usa flexe altri usano flex-grow. Sei sicuro che la flexsottostringa non sarà presente in altre possibili transizioni CSS? Anche se sei sicuro, i lettori del codice ne saranno sicuri? Passerei a un ===test contro entrambe le possibilità, o almeno userei startsWith(che è un po 'più appropriato di .includesqui, poiché entrambe le possibilità iniziano con flex).
Puoi anche spostare il commento sul nome dell'evento di transizione nel JS e nel CSS.
Impostazione concisa di classList Quando si desidera aggiungere un nome di classe o rimuovere un nome di classe, in base a una condizione, è possibile condensare un if(...) classList.add(...) else(...) classList.removein un unico classList.togglecon un secondo argomento che indica se aggiungere o rimuovere la classe. Il tuo
if(panel.classList.contains('open')) {
panel.classList.add('open-active');
} else {
panel.classList.remove('open-active');
}
semplifica a
const { classList } = panel;
classList.toggle('open-active', classList.contains('open'));
Compatibilità del browser Tuttavia, alcuni browser antichi non supportano il secondo argomento, quindi considera che tipo di browser devi supportare. Se vuoi supportare solo browser ragionevolmente aggiornati, va bene. Un'altra cosa da tenere a mente è che è NodeList.prototype.forEachstata introdotta solo pochi anni fa, intorno al 2016 o 2017 IIRC; come startsWith, è più recente di ES6, quindi usa un polyfill o usa invece iteratori e Babel, ad esempio:
for (const panel of panels) {
// do stuff with panel
(se vuoi supportare IE, dovresti comunque usare Babel, per trasferire il tuo codice alla sintassi ES5)
Ritorno nullo? panels.forEach(panel => panel.addEventListenerrestituisce il valore della chiamata addEventListeneral chiamante di forEach. Dal momento forEachche non guarda cosa restituiscono i suoi callback, questo non fa nulla. Non è un vero problema, ma alcuni potrebbero considerare il codice un po 'più sensato se il forEachcallback restituisse void (nessuna returnistruzione o ritorno implicito). (Descritto in TSLint di TypeScript qui )
Pannelli cliccabili Dato che i pannelli sono cliccabili, è possibile cambiare dal cursore predefinito a cursor: pointerper rendere più ovvio all'utente che devono essere cliccati?
Lo spazio tra gli elementi nei selettori che vorrei cambiare .panel>*a .panel > *- lo rende un po 'più facile da leggere quando gli elementi separati sono separate da spazi.
Pannelli ripetitivi Piuttosto che
<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);
}
Considera l'utilizzo :nth-childinvece, che consente di rimuovere panel#completamente le classi extra .
.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>
Revisione
Sono d'accordo con la risposta di CertainPerformance: il codice sembra piuttosto buono. Il rientro sembra coerente, i nomi delle variabili sono appropriati e le righe sono terminate bene. La leggibilità è ottima.
Cicli inefficienti
Il codice nei gestori di eventi si ripete su tutti gli elementi del pannello, ma al massimo solo due elementi avrebbero aggiornamenti al loro elenco di classi. Vedere il suggerimento di seguito sui modi per eliminare i loop.
Regole CSS in eccesso
Lo stile font-size: 20px;non è necessario in .panelpoiché la stessa regola è specificata anche su html, in più viene sovrascritta da selettori più specifici.
Suggerimento
Rimuovere i loop utilizzando la delega degli eventi
Invece di aggiungere listener di eventi a ogni elemento del pannello, è possibile aggiungere listener di eventi all'elemento contenitore. Ciò richiederebbe la modifica dei gestori di eventi per esaminare il target dell'evento e determinare se il target corrispondeva a un pannello o figlio di un pannello - può essere fatto con il .closest()metodo. E una raccolta HTMLC live di elementi con il nome della classe openpuò essere recuperata una volta utilizzando document.getElementsByClassName('open');. Quindi se qualche elemento ha quella classe quando viene chiamato il gestore dei clic, il nome della classe può essere rimosso.
Ciò consentirebbe di aggiungere e rimuovere pannelli senza la necessità di registrare su di essi i gestori di eventi. Sebbene possa non fare una differenza notevole su una piccola pagina come questa, è saggio considerare i punti in cui è possibile evitare un loop.
Nello snippet di codice seguente, i loop sono stati eliminati.
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>