CSS First. Когда JavaScript не нужен

Никита Дубко, HR Tech Яндекса

CSS First.
Когда JavaScript не нужен

Никита Дубко, HR Tech Яндекса

Никита Дубко

Веб-стандарты

JavaScript везде

Web Almanac

📱 2019 KB

Page Weight

🖥️ 2314 KB

Page Weight

🤔 Заменить на CSS?

JS vs CSS

На сайт нужна каруселька!
Заказчик
ChatGPT, помоги найти хорошую карусельку в npm.
swiper
<link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"
/>

<script
    src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"
></script>

🤩 PROFIT!

gzip_size() {
    echo "Plain: $(curl -s $1 | wc -c)";
    echo "Gzipped: $(curl -s $1 | gzip | wc -c)";
}

gzip_size https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js
Plain: 148159
Gzipped: 41120

gzip_size https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css
Plain: 18435
Gzipped: 4845

41 120 + 4 845 = 45 965

Топ-200 в LiveInternet

100 000 DAU

1 запрос в день

30 дней в месяце

100 000 × 30 × 45 965 = 137 895 000 000

137.895 Гб

0.522 ₽ за гигабайт

72 ₽ в месяц

100 000 000 DAU

72 000 ₽ в месяц

Скорость мобильного интернета

1 Мбит/с = 125 Кб/с

370 мс + 600 мс RTT =
970 мс

🤨 Манипуляция
числами

Отчасти 🙃

Карусельку хочу, чтобы карточки свайпать с товарами!
Но красиво, с прилипанием.
Заказчик

🧐 CSS?

Building a media scroller component
<ul class="horizontal-media-scroller">
    <li>
        <a href="#">
            <figure>
                <picture>
                    <img alt="..." loading="lazy" src="..." />
                </picture>
                <figcaption>Image Caption</figcaption>
            </figure>
        </a>
    </li>
    <li>...</li>
</ul>
.horizontal-media-scroller {
    display: grid;
    grid-auto-flow: column;
    grid-gap: calc(var(--gap) / 2);

    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gap);
}

.horizontal-media-scroller figure {
    scroll-snap-align: start;
}
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
scroll-snap-align: start;

gzip_size http://127.0.0.1:8080/demo/style.min.css
Plain: 2855
Gzipped: 993

45 965 993 = 44 972

⬇️ 97%

🤨 Опять манипуляция

☝️ Уточняйте требования заказчика

Simple Slider
scroll-snap-slider@3.1.1
CSS Scroll Snap
Нужно по клику на ссылку плавно скроллить к блоку!
Заказчик
Smooth Scroll

gzip_size https://cdn.jsdelivr.net/gh/cferdinandi/smooth-scroll/dist/smooth-scroll.polyfills.min.js
Plain: 6561
Gzipped: 2709

scroll-behavior: smooth;
Дока: scroll-behavior

🤨 А если sticky-шапка?

scroll-margin-top: 100px;
@media (prefers-reduced-motion: no-preference) {
    .smooth-scroll {
        scroll-behavior: smooth;
    }
}

gzip_size http://127.0.0.1:8080/demo/smooth.min.css
Plain: 86
Gzipped: 93

2 709 93 = 2 616

⬇️ 96.5%

CSS Scroll-behavior
Хочу параллакс по скроллу!
Заказчик
react-scroll-parallax
react-scroll-parallax@3.4.3

x.com/jh3yy

.pop {
    view-timeline-name: --pop;
}
img {
    animation: slide both;
    animation-timeline: --pop;
    animation-range: entry 100% cover 50%;
}
.skateboarder {
    --x: 0;
    --y: -45%;
}
@keyframes slide {
    to { translate: var(--x) var(--y); }
}
jh3yy tweet
animation: some 2s linear infinite;
animation-name: some;
animation-duration: 2s;
animation-timing-function: linear;
animation-iteration-count: infinite;

2s = 100%

Scroll Timeline Visualizer
@keyframes grow-progress {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
}

#progress {
    position: fixed;
    left: 0; top: 0;
    width: 100%; height: 1em;
    background: red;

    transform-origin: 0 50%;
    animation: grow-progress auto linear;
    animation-timeline: scroll();
}
animation-timeline: scroll();
animation-timeline: scroll(nearest block);
.container {
    scroll-timeline-name: --scroll-timeline;
    scroll-timeline-axis: inline; /* or block */
}

.child {
    animation-timeline: --scroll-timeline;
}
readprogress
Animate elements on scroll with Scroll-driven animations
Range and Animation Progress Visualizer
.pop {
    view-timeline-name: --pop;
}
img {
    animation: slide both;
    animation-timeline: --pop;
    animation-range: entry 100% cover 50%;
}
.skateboarder {
    --x: 0;
    --y: -45%;
}
@keyframes slide {
    to { translate: var(--x) var(--y); }
}
jh3yy tweet
Scroll-driven Animations
CSS property: scroll-timeline
flackr/scroll-timeline

gzip_size https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js
Plain: 39988
Gzipped: 11512

Interop 2024

Interop 2024
Interop 2023
@keyframes detect-scroll {
    from, to { --can-scroll: ; }
}

.container {
    animation: detect-scroll linear;
    animation-timeline: scroll(self);

    --bg-if-can-scroll: var(--can-scroll) lime;
    --bg-if-cant-scroll: red;
    background: var(--bg-if-can-scroll, var(--bg-if-cant-scroll));
}
Detect if an element can scroll or not
Style an element based on the active Scroll Direction and Scroll Speed
Ребята, тут в Safari
какой-то баг...
Заказчик
Кнопка «Корзины» не прилипает к низу!
Заказчик
The large, small, and dynamic viewport units
.my-element {
    height: calc(var(--vh, 1vh) * 100);
}

const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);

window.addEventListener('resize', () => {
    const vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty('--vh', `${vh}px`);
});
Safari Viewport Bug
height: 100dvh;
Small, Large, and Dynamic viewport units
Хочу нажимать на картинку, а она чтобы на весь экран открывалась.
Заказчик

gzip_size https://unpkg.com/focus-trap@7.5.4/dist/focus-trap.umd.min.js
Plain: 10717
Gzipped: 3555

🤔 CSS?

<a href="#img1">
    <img src="./small.jpg">
</a>

<a href="#" class="lightbox" id="img1">
    <span style="background-image: url('./large.jpg')"></span>
</a>
.lightbox {
    display: none;

    position: fixed;
    z-index: 999;
    top: 0; left: 0;
    right: 0; bottom: 0;

    background: rgba(0, 0, 0, 0.8);
}

.lightbox:target {
    display: block;
}
CSS selector: :target
Фокус не должен выходить за пределы попапа.
ГОСТ Р 52872-2019
focus-trap
<article class="content" inert>
    <!-- content -->
</article>

<dialog open>
    Content
</dialog>
inert
HTML attribute: inert
Building a dialog component
<dialog>
    <form method="dialog">
        <button>Close</button>
    </form>
</dialog>
dialog.showModal();
dialog
Хочу аккордеон в навигации!
Заказчик
react-accordion
react-accordion@1.1.2
import * as Accordion from '@radix-ui/react-accordion';

() => (
    <Accordion.Root>
        <Accordion.Item>
            <Accordion.Header>
                <Accordion.Trigger />
            </Accordion.Header>
            <Accordion.Content />
        </Accordion.Item>
    </Accordion.Root>
);
accordion

🤔 CSS?

🙃 HTML

<details name=accordion>
    <summary>Exclusive</summary>
    <span>Some Text 1</span>
</details>
<details name=accordion>
    <summary>Accordion</summary>
    <span>Some Text 2</span>
</details>
<details name=accordion>
    <summary>Pattern</summary>
    <span>Some Text 3</span>
</details>
<details name="common-name">
details name
Хочу натуральную анимацию падающего мячика!
Заказчик
GSAP
The Path To Awesome CSS Easing With The linear() Function
animation-timing-function: linear(0, 1);
Linear Easing Generator
Easing Linear
CSS.supports(
    'animation-timing-function',
    'linear(0, 1)'
)
Давайте ускорим сайт! Что-то слышал про Core Web Vitals.
Заказчик

🦥 Ленивая загрузка?

vanilla-lazyload
<img src="photo.jpg" loading="lazy">
<iframe src="frame.html" loading="lazy">
loading=lazy
Можем сделать панельки, которые изменяют размер?
Заказчик
.panels {
    display: grid;
    grid-template-columns: auto 1fr; /* 👈 */
}

.resizer {
    max-width: 100cqi;
    overflow: hidden;
    resize: horizontal; /* 👈 */
}
CSS: resize

CSS First.
Когда JavaScript не нужен

Никита Дубко, HR Tech Яндекса

JavaScript First.
Когда CSS
не нужен

Никита Дубко, HR Tech Яндекса

📚 Знайте свои инструменты

🗞️ Будьте в курсе

Веб-стандарты

Давайте жить дружно!

mefody.dev/talks/css-first/
@dark_mefody
t.me/mefody_dev QR-код со ссылкой на доклад