Добрый день, Хабр! Решил поделиться своим небольшим, но полезным открытием в плане использования html data-attributes & css selectors.
Html data-attributes - это кастомные атрибуты, которые вы можете сами назначать куда-угодно и с каким угодно именем (но имя должно начинаться с префикса data-
). Затем вы можете использовать их в css селекторах, чтобы влиять на содержимое классов и уже классами управлять элементами. Движок браузера автоматически среагирует на изменение data-атрибута и применит соответствующий код css класса.
Код реакт компоненты, где по нажатию кнопки что-то скрывается, что-то показывается:
import React, {useState} from "react";
const ComponentWithExpandableContent = () => {
const [expanded, setExpanded] = useState(false)
return <div style={{
display: 'flex',
flexFlow: 'column nowrap',
alignItems: 'start',
padding: 32,
gap: 32,
}}>
{ !expanded && <div>Initial content</div> }
<button onClick={()=>setExpanded(!expanded)}>
{ !expanded ? 'Expand' : 'Collapse' }
</button>
{ expanded && <div>Additional content</div> }
{ expanded && <div>More additional content</div> }
</div>
}
export default React.memo(ComponentWithExpandableContent)
Состояние expanded=false
:
Состояние expanded=true
:
Теперь просто html css js
.
Файл emulating-use-state.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
Эмулируем React useState в обычном JS
(via data-attributes & css selectors)
</title>
<link rel="stylesheet" type="text/css" href="emulating-use-state.css">
</head>
<body>
<script>
function toggleExpand(){
const element = document.getElementById('component')
if (element.dataset.expanded!=='true'){
element.dataset.expanded = 'true'
} else {
element.dataset.expanded = 'false'
}
}
</script>
<div class="someElementContext">
<!-- data-expanded хранит состояние -->
<div id="component" class="component" data-expanded="false">
<div class="collapsableContent">
<div>Initial content</div>
</div>
<button onClick="toggleExpand()">
<div class="collapsableContent">Expand</div>
<div class="expandableContent">Collapse</div>
</button>
<div class="expandableContent">
<div>Additional content</div>
</div>
<div class="expandableContent">
<div>More additional content</div>
</div>
</div>
</div>
</body>
</html>
Файл стилей emulating-use-state.scss:
.someElementContext {
display: contents;
.component {
display: flex;
flex-flow: column nowrap;
align-items: start;
padding: 32px;
gap: 32px;
// Классы, управляющие видимостью элементов на странице
// в зависимости от текущего значения data-expanded
.expandableContent {
display: none;
}
.collapsableContent {
display: contents;
}
&[data-expanded=true] .expandableContent {
display: contents;
}
&[data-expanded=true] .collapsableContent {
display: none;
}
}
}
Сгенерированный средой разработки emulating-use-state.css:
.someElementContext {
display: contents;
}
.someElementContext .component {
display: flex;
flex-flow: column nowrap;
align-items: start;
padding: 32px;
gap: 32px;
}
.someElementContext .component .expandableContent {
display: none;
}
.someElementContext .component .collapsableContent {
display: contents;
}
.someElementContext .component[data-expanded=true] .expandableContent {
display: contents;
}
.someElementContext .component[data-expanded=true] .collapsableContent {
display: none;
}
Получилось поведение аналогичное описанному в реакт компоненте.
Здесь в качестве стэйта выступает кастомный data-атрибут data-expanded
- он и хранит состояние. А css селектор [data-expanded=true]
изменяет свойство display
из none
в contents
(display: contents
означает показывать содержимое элемента так, как будто самого элемента не существует) и обратно в специальных классах-обёртках .expandableContent
& .collapsableContent
, которыми можно просто обернуть любые элементы в html разметке.