DevGang
Авторизоваться

Эксперт CSS: Взлом процессора

«Взлом ЦП» подразумевает разблокирование возможности непрерывной обработки данных и переоценки состояния.

Например, если бы циклические переменные автоматически не переходили в недопустимое (initial) состояние в CSS, это будет постоянно увеличивать значение --frame-count здесь:

body {
  --input-frame: var(--frame-count, 0);
  --frame-count: calc(var(--input-frame) + 1);
}

Спойлер: вы действительно можете сделать это с помощью CSS, даже не прикасаясь к JS, я покажу вам, как!

5 Наблюдений

Во-первых, давайте проведем несколько наблюдений за использованием расширенной CSS-анимации, чтобы окончательная демонстрация не была совершенно неожиданной.

1. Правила состояния анимации для всех (почти)

Назначения свойств, заданные состоянием анимации, превосходят все назначения свойств состояния селектора.

В этом примере фон тела всегда hotpink:

  body {
    animation: example 1s infinite;
    --color: blue;
    background: var(--color);
  }
  body:hover {
    --color: green;
  }
  body:has(div:hover) {
    --color: red;
  }
  @keyframes example {
    0%, 100% { --color: hotpink; }
  }

Именно поэтому (частично) состояние анимации не позволяет изменять свойства, управляющие анимацией. Например, вы не можете анимировать значение animation-play-state.

В противном случае после запуска самонастраивающуюся анимацию можно было бы остановить только путем удаления JS элемента, в котором она находится, поскольку анимация могла бы установить свое собственное значение animationи остаться в живых независимо от того, какие другие состояния селектора пытались ее остановить.

Приостановленная анимация не является исключением; какое бы значение ни было во время паузы, оно все равно превосходит другие состояния.

2. При назначении свойств в Keyframe можно использовать var()

body {
  animation: example 1s infinite;
  --color: blue;
}
body:hover {
  --color: green;
}
body:has(div:hover) {
  --color: red;
}
@keyframes example {
  0%, 100% { background: var(--color); }
}

В этом примере цвет фона по умолчанию blue, green при наведении или red при наведении курсора на элемент div. Цвет меняется по мере взаимодействия пользователя.

3. --var Назначения результатов Keyframe кэшируются

Мы можем проверить это, добавив небольшую косвенность к назначению цвета фона:

body {
  animation: example 1s infinite;
  --color: blue;

  background: var(--bg);
}
body:hover {
  --color: green;
}
body:has(div:hover) {
  --color: red;
}
@keyframes example {
  0%, 100% { --bg: var(--color); }
}

Несмотря ни на что, фон всегда blue, потому что сначала он оценивается как blue, а изменения на --color не пересчитываются.

Даже если анимация paused, кэшированное значение не меняется при изменении состояний фона.

Приостановленные анимации используют кэшированное значение.

4. Изменение свойства анимации нарушает кеш

Изменяя animation-duration при :hover(наведения) пользователя, кэш анимации пересчитывается.

body {
  animation: example 1s infinite;
  --color: blue;
  background: var(--bg);
}
body:hover {
  --color: green;
  animation-duration: 2s;
}
body:has(div:hover) {
  --color: red;
  animation-duration: 3s;
}
@keyframes example {
  0%, 100% { --bg: var(--color); }
}

Конечный результат здесь точно такой же, как в пункте 2 выше; цвет фона по умолчанию blue, green при наведении или red при наведении курсора на элемент div.

Примечание. В Safari есть ошибка, из-за которой он НЕ пересчитывает кэш при изменении свойства анимации, поэтому мы вошли на территорию только Chrome (Firefox пока не может анимировать --vars)

Если бы мы «изменили» animation-duration на , технически она не изменилась бы, и кеш не будет пересчитываться.

Вы начнете получать интересное поведение, если оба состояния :hover используют одно и то же значение, отличное от состояния по умолчанию.

body {
  animation-duration: 1s;
}
body:hover {
  animation-duration: 2s;
}
body:has(div:hover) {
  animation-duration: 2s;
}

Давайте покажем это вживую:

В зависимости от того, где ваша мышь входит в экран (сверху или снизу), вы получите разные цвета, которые «привязываются» к одному или другому, пока вы не уберете мышь.

5. Две анимации

Что, если вместо изменения состояния псевдоселектора --color мы создадим другую анимацию, которая его изменит?

В нашем example анимации по-прежнему устанавливается --bg на основе --color, поэтому мы можем ожидать, что она будет иметь такое же поведение кеширования.

Изменение свойства анимации нашего example также должно привести к перерасчету ее кэша.

Итак, наконец, example анимации должен принимать любое текущее значение --color из другой анимации и кэшировать его вместе с его состоянием.

Вот как это будет выглядеть:

body {
  animation: color 3s step-end infinite,
    example 1s infinite;

  background: var(--bg);
}
body:hover {
  animation-play-state: running, paused;
}
div::after {
  content: "color preview";
  background: var(--color);
}

@keyframes color {
  0%, 33% { --color: blue; }
  33%, 67% { --color: green; }
  67%, 100% { --color: red; }
}
@keyframes example {
  0%, 100% { --bg: var(--color); }
}
Примечание. Несмотря на то, что мы приостанавливаем example анимации при наведении курсора мыши, это по-прежнему представляет собой изменение состояния running по умолчанию, поэтому оно повторно вычисляет и приостанавливает работу в том же кадре рисования CSS.

и вот на что это похоже:

Фон фиксируется на том, что было, когда вы вошли, затем пересчитывается и снова фиксируется на том, что было, когда вы уходите.

Взлом процессора начинается

Предыдущая информация подразумевает нечто чрезвычайно интересное; получение кэшированного значения из анимации не приводит к его повторному вычислению, поэтому оно не должно вызывать недопустимое циклическое состояние, если источник кэшированного значения удален на один шаг.

Двойной захват, один раз вычисление, управление временем... Должно быть возможно.

У нас есть example анимации, условно фиксирующей значение либо из обычных состояний селектора, либо из другой анимации.

Давайте представим, что он фиксирует число вместо цвета, как, например, --frame-count из начала этой статьи.

И мы переименуем его из example в capture.

body {
  animation: capture 1s infinite;

  --input-frame: 0;
  --frame-count: calc(var(--input-frame) + 1);
}
@keyframes capture {
  0%, 100% { --frame-captured: var(--frame-count); }
}

Было бы здорово, если бы мы могли установить --input-frame в это значение --frame-captured?

Мы знаем, что выполнение этого напрямую будет циклическим, поскольку все три назначения существуют в одном кадре:

--input-frame = --frame-captured
--frame-count = --input-frame + 1
--frame-captured = --frame-count  

Если мы захватим захваченное значение и убедимся, что оба захвата не выполняются одновременно, захват-захват может поднять это значение обратно в --input-frame...

Давайте попробуем. Мы вызовем захваченный захватный hoist.

Кроме того, поскольку мы не хотим, чтобы они когда-либо запускались одновременно (поскольку это определенно будет циклическим), давайте начнем их с paused, чтобы быть в безопасности.

body {
  animation: hoist 1ms infinite,
    capture 1ms infinite;
  animation-play-state: paused, paused;

  --input-frame: var(--frame-hoist, 0);
  --frame-count: calc(var(--input-frame) + 1);
}
body::after {
  counter-reset: frame var(--frame-count);
  content: "--frame-count: " counter(frame);
}
@keyframes hoist {
  0%, 100% { --frame-hoist: var(--frame-captured, 0); }
}
@keyframes capture {
  0%, 100% { --frame-captured: var(--frame-count); }
}

Теперь, чтобы проверить это, мы также хотим настроить некоторый dom, на который мы можем наводить курсор в определенном порядке, чтобы запускать animation-play-state в правильном порядке. Никаких промежутков между элементами, и мы дадим им классы phase-0 и т. д.

На первом этапе определенно фиксируется исходный результат. Поэтому мы оставим hoist на паузе и сначала запустим наш старый друг capture:

body:has(.phase-0:hover) {
  animation-play-state: paused, running;
}

Мы можем перестать наводить курсор на этот элемент, чтобы приостановить оба, что зафиксирует --frame-count, или мы можем пойти дальше и настроить другой элемент, чтобы явно сделать это:

body:has(.phase-1:hover) {
  animation-play-state: paused, paused;
}

И наконец? Момент истины: проверьте, можем ли мы запустить hoist, пока capture приостановлен, что должно дать нам достаточно места, чтобы избежать циклической зависимости и вернуть этот вывод обратно в верхнюю часть в качестве входных данных... что должно дать нам первые 2

body:has(.phase-2:hover) {
  animation-play-state: running, paused;
}

Вот это в реальном времени: наведите курсор сверху вниз, чтобы завершить цикл:

Взлом процессора

Мы могли бы заставить пользователя гладить dom своим курсором весь день или мы могли бы переместить dom под курсор в тот момент, когда это необходимо, чтобы автоматически вызвать :hover

Давайте разберемся в этом!

Нам понадобится навести курсор на .phase-0, чтобы автоматически «перейти» к .phase-1, а затем навести курсор на «перейти к» .phase-2

А затем при наведении курсора .phase-2 необходимо вернуться в paused, paused состояние, чтобы избежать одновременного выполнения вычислений для обеих анимаций в одном кадре.

Помните: воспроизведение или приостановка анимации приводит к ее повторному вычислению в этом кадре, поэтому переход от running, paused прямо к paused, running фактически означает, что для одного кадра выполняются оба одновременно.

Итак, нам нужно «перейти» в состояние, которое затем «перейдет» к .phase-0. Поскольку .phase-1 ставится на paused, paused и «переходит» к 2, мы продублируем его и заставим новый .phase-3 также приостанавливать оба, но вместо этого «перейти» к 0.

Давайте добавим этот CSS к тому, что у нас было:

body:has(.phase-3:hover) {
  animation-play-state: paused, paused;
}

И мы будем использовать это для HTML:

<ol class="cpu">
  <li class="phase-0"></li>
  <li class="phase-1"></li>
  <li class="phase-2"></li>
  <li class="phase-3"></li>
</ol>

Если интересно, вот краткое описание каждого этапа.

  • .phase-0 (hoist приостановлен, capture работает)
  1. Hoist значение заморожено
  2. Назначить Captured = Выходное значение
  3. Перейти к .phase-1
  • .phase-1 (hoist приостановлен, capture приостановлен)
  1. Hoist значение заморожено
  2. Назначить Captured = Выходное значение
  3. Зафиксировано Captured (в конце этого кадра рисования CSS)
  4. Перейти к .phase-2
  • .phase-2 (hoist работает, capture приостановлен)
  1. Captured значение заморожено
  2. Назначить Hoist = Captured значение
  3. Перейти к .phase-3
  • .phase-2 (hoist приостановлен, capture приостановлен)
  1. Captured значение заморожено
  2. Назначить Hoist = Captured значение
  3. Зафиксировано Hoist (в конце этого кадра рисования CSS)
  4. Перейти к .phase-0

Далее мы стилизуем элемент .cpu, чтобы каждый из его дочерних элементов занимал всю его площадь, когда их ширина становится 100%, располагаясь друг над другом по оси Z в порядке dom.

.cpu { position: relative; list-style: none; }
.cpu > * {
  position: absolute;
  inset: 0px;
  width: 0px;
}
.cpu > .phase-0 { width: 100%; }
.cpu > .phase-0:hover + .phase-1 { width: 100%; }
.cpu > .phase-1:hover + .phase-2 { width: 100%; }
.cpu > .phase-2:hover + .phase-3 { width: 100%; }

Это должна быть последняя часть; каждая фаза запускает следующую, и каждая из них происходит только для одного кадра рисования CSS. Давайте посмотрим это вживую!

Примечание: нам также необходимо зарегистрировать выходную переменную (--frame-count), иначе она внезапно перестанет работать при значении 100 из-за того, что функция calc() технически становится вложенной на каждой итерации. Приведение его к целому числу предотвращает это и является гораздо более эффективным. В приведенной выше демонстрации включен код @property.

Кроме того, технически вы можете сделать одну небольшую очистку:

Отбросьте переменную --input-frame, просто --frame-hoist напрямую, так будет чище.

Остальная часть вопроса

Итак, у вас есть процессор в CSS. Что вы можете сделать с этим?

100% CSS Compute Integer --width and --height of the Screen
100% CSS Image-Mouse-Coordinate Zoom on Hover
100% CSS Conway's Game of Life Simulator - Infinite Generations, 42x42

100% CSS Breakout, играйте здесь:

Источник:

#CSS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу