Скрол-історія
Сцена на всю висоту екрана, плівки, що передають одна одній хід, і ШАРИ, які
приїжджають поверх них, тримаються і йдуть: спершу титульна картка сторінки, далі твердження по
одному на екран. Плівок шість, і разом вони одна дія: порожня лава - коробка наповнюється - кожна
банка отримує чисту картку - стулки закриваються - стрічка і наліпка - руки забирають коробку. Позиція скролу обирає кадр, тож ролики не ПРОГРАЮТЬСЯ, а ПЕРЕМОТУЮТЬСЯ: десять
секунд знятого покривають секцію будь-якої висоти, а той, хто спинив скрол, спиняє коробку на
півдорозі, а не дивиться, як вона тікає.
12.21 переписав дві речі. Перша: плівка стала ПЕРШИМ екраном, і герой
сторінки живе на ній титульною карткою, а не блоком над нею. Друга: усі шари поїхали на
годиннику скролу. До того плівка перемотувалась щокадру, а твердження перемикав
IntersectionObserver - булеве спрацювання, яке віддає CSS 330 мс переходу гратись
самому. Плівка пливла, текст клацав, і жодна крива це не лікує, бо два годинники міряли різне.
Живий приклад
Це справжня секція, а не знімок: ті самі дві плівки і ті самі п'ять тверджень, що на
лендингу тренера. Від 860 сцена займає весь кадр продукту і липне, твердження лягають на неї
чорнильними плитками і зникають, коли кадр іде далі; вужче - сцена стає звичайною картинкою 16:9,
а твердження звичайними боксами, і жоден лист не запитується з мережі.
Тренерам · залам · командам
Спортивне харчування оптом для тренерів
Титульна картка - це той самий шар, що й твердження нижче: та сама
обгортка, та сама плитка, та сама обвідна. Різниця лише в тому, що всередині неї.
Дрібний рядок під кнопками приїжджає останнім: пороги каскаду читають
одне й те саме число і стартують по черзі.
- 💰 Гуртові цінина кожному товарі в сесії, а не на підсумку
- 👥 Одна сесія - усі клієнтидодавайте кожному за його ціллю, не виходячи з кошика
- ⏱️ Історія по кожному клієнтуповтор попереднього замовлення в один тап
- 🚚 Одна доставка на тренераодне відправлення, розподіл за клієнтами всередині
- 🛡️ Надійний склад і постачаннясклад, походження і сертифікат на кожен товар
Коли вживати
Коли твердження треба не перелічити, а вивести одне з одного. Сітка відповідає
на питання «що», плівка відповідає на «звідки це взялось»: коробка наповнюється, БО тренер додає
ще одного клієнта, і кожне твердження підіймається поруч із тим кадром, який його заробив.
Це і є робота «зв'язок» з motion.md.
Тільки на сторінці, яка продає. Товарний екран, кошик, оформлення і кабінет
цього компонента не беруть ніколи: там людина виконує задачу, і липка сцена забирає в неї півекрана
заради аргументу, який вона вже прийняла.
Обмеження
Плівка приходить ОДНИМ зображенням. 41 кадр, викладені 6x7, кожна комірка
800x450; drawImage копіює одну комірку на кадр. Зміряно на справжньому ролику:
лист 1006 KB проти 2190 KB тих самих 41 кадру як mp4 і 1280 KB як 41 окремий
jpg. Тобто найлегший із трьох, ОДИН запит замість сорока одного, і жодного відеодекодера -
саме там перемотуваний <video> і затинається.
Плівок стало шість, і саме тому з'явилось відкладене завантаження. Дві плівки
на шість екранів це 50-70 пікселів скролу на кадр - коробка наповнюється ривками, бо між
двома сусідніми кадрами півекрана прокрутки. Шість плівок, по одній на екран, дають 21-24 пікселі
на кадр, і це вже перемотка, а не слайдшоу. Ціна - 5.8 MB листів замість 1.95, і платити
її одразу не можна: лист запитується за один екран до того, як він потрібен, і запас це
відношення, а не число - висота сцени, поділена на хід секції, тобто рівно один екран скролу,
скільки б шарів не було в історії. Зміряно на живій сторінці при 1440x900: на першій фарбі запитано
два листи з шести, третій приходить на чверті ходу, четвертий на третині, п'ятий на 58%,
шостий на трьох чвертях.
Без JavaScript сторінка ЗАВЕРШЕНА, а не зіпсована. Сцена несе справжній
<img> першого кадру; канва лежить зверху з нульовою прозорістю і проявляється
лише тоді, коли на ній справді намальовано кадр. Стан спокою шарів написаний не на них, а під
.story.is-live - клас, який ставить тільки скрипт. Написане навпаки, воно лишило б
сторінку з упалим скриптом із власним H1 на нульовій прозорості назавжди, і жоден прилад цього б не
побачив.
Один .story на сторінку. Дві липкі сцени підряд конкурують за один
екран, і друга починає рухатись раніше, ніж перша доїхала. Плівок усередині сцени може бути
скільки завгодно - кожна оголошує свій відрізок data-from / data-to, і
відрізки НАКЛАДАЮТЬСЯ: у перекритті обидві малюють, а наступна проявляється поверх попередньої. Різ
в одній точці читається як збій, накладення читається як один дубль. На лендингу тренера відрізків
шість, по одному на екран, і кожне перекриття це 0.04 ходу, тобто близько 180 пікселів
скролу.
Кожна плівка починається СПРАВЖНІМ останнім кадром попередньої, а не
намальованим наближенням до нього. Kling 2.5 на 720p вимагає стартовий кадр і забороняє кінцевий,
тож ланцюг тут вимушений і послідовний: зняли сцену, витягли її останній кадр, віддали наступній.
Ціна - сцени не можна генерувати паралельно. Виграш - лава, світло, підлога і сама коробка збігаються
на стику до пікселя, і перекриття ховає рух, а не розбіжність.
Стани
Клас лишився один. .story.is-live - скрипт узяв секцію: тільки з
нею шари мають стан спокою. .is-on і .is-in, які тут стояли до 12.21,
ВИДАЛЕНІ, і це не прибирання, а наслідок: обидва були булевими, а стан кожного шару тепер
неперервний, і назвати класом його неможливо.
Замість них скрипт пише три числа, і всі три без одиниць:
Жодне з трьох ніколи не потрапляє в transition, і це ремонт, а не
стиль: у першій збірці на плівці стояв transition: opacity, скрип переписував значення
щокадру, кожен кадр перезапускав 330 мс, і значення не доїжджало ніколи. Зміряно на передачі ходу:
обидві плівки на 0.008, обидві малюють правильні кадри. Плівка, яка працює бездоганно і якої
не видно. Рампа між двома рухомими кінцями не рампа.
Жодного :hover на самій історії: тут нема на що натискати. Виняток -
кнопки в титульній картці, і вони отримали грань замість тла, бо тла для темної плитки в системі
немає, а вигадувати токен під ховер не можна.
Рух
Робота: ЗВ'ЯЗОК. Рухаються рівно дві властивості - opacity і
transform, бо це ті дві, які браузер міняє не чіпаючи розкладку. Ні пружини, ні
відскоку, ні часток, ні лічильника - принцип 4 продукту каже «спокійно і впевнено», і плівка тут
аргумент, а не феєрверк.
Тривалості немає. І це не пропуск: рух тут не має власного годинника, бо ним
керує скрол. --dur-* і --ease-* у файлі не читаються ЖОДНОГО разу, а
криву дає не токен, а smoothstep на плечі обгортки - те, що знімає з приїзду кут.
Дистанція одна, --move-lg, і вона нова: --move-md це 10px, хід елемента
ВСЕРЕДИНІ сторінки, а 10px на плитці, яка володіє цілим екраном, це не рух, а тремтіння.
--move-lg це 40px, і 40 тут відношення, а не здогад - шар проходить свій власний
відступ, --space-40.
Хореографія - це одна формула на індексі, а не список порогів. До 12.22 тут
стояли чотири вручну набрані числа - окреме для кікера, для заголовка, для ліда, для кнопок - і
п'ятий рядок у картці не мав би де стати в чергу. Тепер скрипт пише --i (місце дитини
всередині плитки) і --w (місце слова всередині заголовка), крок написаний ОДИН раз, а
--story-s це частка дитини в обгортці її шару. Додали рядок - він бере свою чергу; забрали
- решта зімкнулась.
Твердження ПАРКУЮТЬСЯ, а не проходять повз - 12.23. До того кожне володіло
своїм екраном і віддавало його назад: піднялось, потрималось, згасло, наступне приїхало. Тепер
кожне приїжджає у свою точку скролу і ЛИШАЄТЬСЯ - значення впирається в 1, і гілки назад немає, -
а до кінця історії всі п'ять стоять на дошці разом. Це те, чого сторінка, яка продає, хоче саме в
ту мить, коли коробку забирають.
Дошка це один прибитий екран, і твердження лежать на ній двома колонками до
країв - непарні ліворуч, парні праворуч, - тож плівці лишається середина. Стос росте ЗНИЗУ
вгору, подалі від шапки: сама сцена прибита на top: 0 навмисно, бо фотографія під
плаваючою смугою це саме те, як виглядає повноекранний герой, а от текст під тією ж смугою це просто
текст, якого не прочитати.
Висота секції перестала бути побічним ефектом. Поки кожне твердження мало свій
рядок на 100svh, ці рядки і БУЛИ довжиною скролу; запарковані на одній дошці вони всі заввишки в
один екран, і секція згорнулась би до одного екрана, а плівці не було б що перемотувати. Тепер
довжина написана тим відношенням, яким вона завжди була - один екран на шар, - а число шарів дає
скрипт, бо він єдиний їх бачить. Сторінка з упалим скриптом падає на 1: секція рівно
заввишки зі свою сцену, без плівки і без прибивання.
Заголовок приїжджає СЛОВО ЗА СЛОВОМ, і це єдине місце, де історії дозволено
показувати себе. Сам заголовок перестає згасати - його несуть слова, - а крок між словами КОРОТШИЙ
за крок між блоками: речення читається на одному диханні, і довга пауза між словами читається як
затинання, а не як фраза.
І одна річ, яка МАЛЮЄТЬСЯ, а не згасає. Тонка помаранчева риска росте від того
краю, до якого притиснута плитка - непарні ліворуч, парні праворуч. Шість однакових приїздів на шість
екранів це знову список; риска дає інший рід руху за ті самі дві властивості, бо scaleX
це transform.
Бібліотеки скрол-таймлайну тут немає, і це рішення, а не пропуск. GSAP
ScrollTrigger зі scrub робить рівно те, що робить арифметика вище: чіпляє прозорість і
хід до позиції скролу замість власного годинника. Те, чого від нього хочуть насправді - каскад,
розбивка заголовка на слова, риска, - це ХОРЕОГРАФІЯ, тобто авторська робота, і вона однакова з
бібліотекою і без неї. Різниця лише в ціні: 70 KB з CDN у пакеті, який зобов'язаний підніматись без
збірки й без мережі (тест чистого клону), плюс залежність без власника в системі, де їх нуль.
При prefers-reduced-motion перемотки немає взагалі: лишається
постер, а шари просто показані. --move-lg складається сам через перевизначення в
tokens.css, але обгортку він не дістає - прозорість це СТАН, а не тривалість, і блок у
кінці файлу підіймає кожен шар до непрозорого рукою. Елемент, що перестав з'являтись, був би гіршим
дефектом за будь-яку анімацію.
Ширина
360 - це не маленький десктоп. Нижче --bp-shell-wide немає ні
липкості, ні канви, ні листів: жоден із двох не запитується з мережі, і це зміряно на живій
сторінці, а не обіцяно. Сцена - звичайна картинка 16:9, твердження - та сама сітка боксів 2 / 3, що
стояла тут до історії. Прибитий на весь екран ролик це те місце, де такі сторінки вмирають на
телефоні, і найдешевший спосіб не вмерти - там не бути.
На всю ширину - це ширина КАДРУ ПРОДУКТУ, і вона зміряна, а не обчислена.
Перша збірка виходила лише за власні 16px відступу сторінки, і поки колонка сторінки дорівнює кадру,
цього досить: на 1280 кадр 1064 і колонка 1064, дивитись нема на що. Але колонка впирається в
--container-page, тож на 1440 обабіч плівки відкрились дві білі смуги по 12px, на
1600 по 92px, а на 1920 - по 252px. Білі стовпи вздовж повноекранної фотографії, і
вони росли разом з екраном. 100vw тут відповідь неправильна: він рахує класичний
скролбар, якого в кадрі немає, і плівка вийшла б ширшою за сторінку, тобто горизонтальний скрол на
кожному екрані. Тому ширину скрипт ЧИТАЄ з .wf-canvas і публікує як
--story-bleed - те саме, що uivStickyHeader() робить із
--shell-header-h. Запасне значення 100%: сторінка з упалим скриптом
виглядає рівно так, як виглядала.
Точки: 620 - твердження стають трьома в ряд. 860 робить п'ять речей
одразу: сцена виходить на всю ширину кадру продукту і липне на 100svh, титульна картка
підтягується ПОВЕРХ неї на цілу сцену (тому перший екран сторінки і перший екран історії це один
екран), твердження лягають рядами по екрану і стають чорнильними плитками, непарні ліворуч і парні
праворуч, і вмикається скрипт.
Нижче 860 порядок ІНШИЙ, ніж у розмітці. Сцена мусить стояти першою у
розмітці, щоб від 860 бути липким предком усього, що по ній їде; на телефоні ж сторінка, яка
продає, відкривається власним H1, а не картинкою. Одне order розводить два читання, і
жодне не програє.
Токени
Прочитані з scrollstory.css: 11 семантичних ролей і 25 примітивів. Список виводить node tools/roles.mjs scrollstory з самого файла, тож він не може розійтися з ним.