Bảng linh hoạt trong CSS và JS
Tôi đã theo dõi thử thách 30 ngày của Wes Bos JS, vì vậy HTML và CSS chủ yếu là copy-paste, tôi muốn phản hồi về JS (chủ yếu). Cảm ơn.
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>
Trả lời
Nó trông khá tốt với tôi. Tôi chỉ có thể thấy một số điều cần xem xét:
Kiểm tra thuộc tính chính xác hơn Tên bạn có if(e.propertyName.includes('flex')) {vì Safari sử dụng flexvà những người khác sử dụng flex-grow. Bạn có chắc chắn rằng flexchuỗi con sẽ không có trong bất kỳ quá trình chuyển đổi CSS có thể nào khác không? Ngay cả khi bạn chắc chắn, liệu người đọc mã có chắc chắn không? Tôi muốn chuyển sang một ===bài kiểm tra chống lại cả hai khả năng, hoặc ít nhất là sử dụng startsWith(thích hợp hơn một chút so với .includesở đây, vì cả hai khả năng đều bắt đầu bằng flex).
Bạn cũng có thể di chuyển nhận xét về tên sự kiện chuyển đổi sang JS cũng như CSS.
Thiết lập danh sách lớp ngắn gọn Khi bạn muốn thêm tên lớp hoặc xóa tên lớp, dựa trên một điều kiện, bạn có thể kết tụ một if(...) classList.add(...) else(...) classList.removethành một duy nhất classList.togglevới đối số thứ hai cho biết có nên thêm hay bớt lớp hay không. Của bạn
if(panel.classList.contains('open')) {
panel.classList.add('open-active');
} else {
panel.classList.remove('open-active');
}
đơn giản hóa thành
const { classList } = panel;
classList.toggle('open-active', classList.contains('open'));
Khả năng tương thích của trình duyệt Mặc dù vậy, một số trình duyệt cổ không hỗ trợ đối số thứ 2, vì vậy hãy xem xét loại trình duyệt nào bạn cần hỗ trợ. Nếu bạn chỉ muốn hỗ trợ các trình duyệt cập nhật hợp lý, thì tốt thôi. Một điều cần lưu ý là nó NodeList.prototype.forEachchỉ được giới thiệu một vài năm trước đây, khoảng 2016 hoặc 2017 IIRC; chẳng hạn như startsWithnó mới hơn ES6, vì vậy hãy sử dụng polyfill hoặc sử dụng các trình vòng lặp và Babel để thay thế, ví dụ:
for (const panel of panels) {
// do stuff with panel
(nếu bạn muốn hỗ trợ IE, bạn vẫn nên sử dụng Babel, để chuyển mã của bạn sang cú pháp ES5)
Trở lại hư không? panels.forEach(panel => panel.addEventListenertrả về giá trị của việc gọi addEventListenerđến người gọi của forEach. Vì forEachkhông xem xét những gì các lệnh gọi lại của nó trả về, điều này không làm được gì cả. Đó không phải là một vấn đề thực sự, nhưng một số người có thể coi mã có ý nghĩa hơn một chút nếu lệnh forEachgọi lại trả về void (không có returncâu lệnh hoặc trả về ngầm định nào cả). (Được mô tả trong TSLint của TypeScript tại đây )
Các bảng có thể nhấp Vì các bảng có thể nhấp được, có thể thay đổi từ con trỏ mặc định thành cursor: pointerđể làm cho người dùng rõ ràng hơn rằng họ muốn được nhấp?
Khoảng cách giữa các yếu tố trong selectors Tôi muốn thay đổi .panel>*để .panel > *- nó làm cho nó một chút dễ dàng hơn để đọc khi các yếu tố riêng biệt được ngăn cách bởi khoảng trắng.
Bảng lặp lại thay vì
<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-childThay vào đó, hãy cân nhắc sử dụng , cho phép bạn loại bỏ panel#hoàn toàn các lớp học thêm .
.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>
Ôn tập
Tôi đồng ý với câu trả lời của MostPerformance: mã trông khá đẹp. Thụt lề có vẻ nhất quán, các tên biến phù hợp và các dòng được kết thúc tốt. Khả năng đọc là rất tốt.
Vòng lặp không hiệu quả
Mã trong trình xử lý sự kiện lặp lại trên tất cả các phần tử bảng điều khiển, nhưng tối đa chỉ có hai phần tử sẽ có cập nhật cho danh sách lớp của chúng. Xem gợi ý dưới đây về các cách loại bỏ các vòng lặp.
Quy tắc CSS dư thừa
Kiểu font-size: 20px;không cần thiết theo .panelvì quy tắc tương tự cũng được chỉ định html, cộng với nó sẽ bị ghi đè bởi các bộ chọn cụ thể hơn.
Gợi ý
Xóa các vòng lặp bằng cách sử dụng Ủy quyền sự kiện
Thay vì thêm trình xử lý sự kiện vào từng phần tử bảng điều khiển, trình xử lý sự kiện có thể được thêm vào phần tử vùng chứa. Điều này sẽ yêu cầu thay đổi trình xử lý sự kiện để xem xét mục tiêu sự kiện và xác định xem mục tiêu có khớp với bảng điều khiển hay con của bảng điều khiển hay không - có thể được thực hiện bằng .closest()phương pháp này. Và một bộ sưu tập HTMLC trực tiếp của các phần tử có tên lớp opencó thể được tìm nạp khi sử dụng document.getElementsByClassName('open');. Sau đó, nếu bất kỳ phần tử nào có lớp đó khi trình xử lý nhấp chuột được gọi thì tên lớp có thể bị loại bỏ.
Điều này sẽ cho phép thêm và xóa các bảng mà không cần đăng ký trình xử lý sự kiện trên chúng. Mặc dù nó có thể không tạo ra sự khác biệt đáng chú ý trên một trang nhỏ như thế này, nhưng điều khôn ngoan là nên cân nhắc những nơi có thể tránh được vòng lặp.
Trong đoạn mã dưới đây, các vòng lặp đã được loại bỏ.
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>