JS in CSS

Виталий Зюзин
4 min readMar 20, 2018

--

Да, это не ошибка, сегодняшняя статья именно про JavaScript в CSS.

Вообще, вставлять скрипты в CSS — довольно странная на первый взгляд идея. А появилась она у меня после прочтения фрагмента спецификации кастомных свойств (они же CSS-переменные).

Там написано:

For example, the following is a valid custom property:

--foo: if(x > 5) this.width = 10;

While this value is obviously useless as a variable, as it would be invalid in any normal property, it might be read and acted on by JavaScript.

То есть, говоря по-простому: в кастомное CSS-свойство можно записать код, который позже можно будет прочитать и использовать из JS.

Как и зачем это можно использовать?

Первая мысль была об изощрённой атаке на браузер пользователя. 😈

Для этого нужно заставить жертву скачать два файла: CSS и JS. В CSS объявляется кастомное свойство с названием --script и JS-кодом в качестве значения:

:root {
--script: console.log("hack!");
}

А теперь нам нужен JS-файл, в котором значение --script считается и выполнится:

// Считываем все стили документа
const style = getComputedStyle(document.documentElement);
// Берём значение свойства --script
const script = style.getPropertyValue('--script');
// Выполняем значение --script
new Function(script)();

И это сработает:

С помощью CSS-анимации можно подменять значение кастомного свойства с течением времени и выполнять каждый раз новый код.

Для этого создадим такую анимацию:

:root {
animation-name: hack;
animation-duration: 3s;
animation-delay: 1s;
animation-fill-mode: backwards;
}

В этой анимации в течение трёх секунд будем менять значение свойства --script:

@keyframes hack {
0% {
--script: console.log("🙈");
}
33% {
--script: console.log("🙉");
}
66% {
--script: console.log("🙊");
}
}

В JS будем считывать и выполнять значение свойства --script каждую секунду:

setInterval(() => {
const style = getComputedStyle(document.documentElement);
const script = style.getPropertyValue('--script');

new Function(script)();
}, 1000);

И это тоже сработает:

Этот код есть в демке https://codepen.io/juwain/pen/EEgOdr

Игры в хакеров конечно забавные, но можно ли как-то с реальной пользой применить фичу с JS и кастомными свойствами?

По мотивам идеи Гарри Робертса мне пришла в голову мысль о «логировании» использования CSS-кода.

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

Идея в том, чтобы включить в каждый CSS-файл уникальное кастомное свойство, которое будет ставить отметку о своём существовании в localStorage браузера.

/* ---------------------- Файл style-1.css ---------------------- */:root {
--style-1: localStorage["--style-1"] = new Date();
}
/* ---------------------- Файл style-2.css ---------------------- */:root {
--style-2: localStorage["--style-2"] = new Date();
}
/* ---------------------- Файл style-3.css ---------------------- */:root {
--style-3: localStorage["--style-3"] = new Date();
}
/* и так далее… */

При каждой загрузке страниц сайта можно считывать значения этих кастомных свойств и выполнять записанный в них JS:

const properties = ['--style-1', '--style-2', '--style-3'];
let style = getComputedStyle(document.documentElement);
properties.forEach(property => {
new Function(style.getPropertyValue(property))();
});

Для проверки я сделал две условных сборки стилей. В одну сборку включил первый и третий CSS-файл, а в другую — только первый файл. Второй стилевой файл не использовался:

/* ---------------------- Файл bundle-1.css --------------------- */@import "style-1.css";
@import "style-3.css";
/* ---------------------- Файл bundle-2.css --------------------- */@import "style-1.css";

Затем я сделал две HTML-страницы, на одной из них подключил файл bundle-1.css, а на другой — bundle-2.css. Затем открыл страницы.

В localStorage сохранились «следы»:

Первый стилевой файл оставил самую свежую метку, третий — более старую, а второй вообще не оставил следов.

В localStorage вместо отметки времени можно записывать, например, счётчик и инкрементировать его при каждой загрузке стилей. Так можно составить статистику использования определённых стилей на сайте.

Ещё этот подход интересен тем, что каждое использование стилей будет логироваться, даже если стили загружаются не с сервера, а из кеша браузера.

Наверняка можно придумать ещё варианты использования этой фичи. Если у вас есть идеи, напишите мне в комментах, в твиттере или ещё где-нибудь. ✌️

--

--