Главная » Статьи » Работа с новой объектной моделью CSS Typed Object Model

Работа с новой объектной моделью CSS Typed Object Model

Работа с новой объектной моделью CSS Typed Object Model

От автора: основы использования новой объектной модели CSS Typed Object Model. Сравнение с CSSOM, основные преимущества. Примеры работы со значениями.

В CSS появилась новая объектная модель API для работы со значениями в JS.

el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'

Дни конкатенации строк и тонких багов сочтены!

Внимание: Chrome 66 добавляет поддержку CSS Typed Object Model для подмножества CSS свойств.

Старая CSSOM

Долгое время в CSS существовала объектная модель (CSSOM). По факту, каждый раз при чтении/установке .style в JS вы делаете следующее:

// Element styles.
el.style.opacity = 0.3;
typeof el.style.opacity === 'string' // Ugh. A string!? // Stylesheet rules.
document.styleSheets[0].cssRules[0].style.opacity = 0.3;

Новая CSS Typed OM

Новая CSS Typed Object Model (Typed OM) или типизированная объектная модель является частью усилий Houdini и расширяет ее мировоззрение, добавляя типы, методы и правильную объектную модель к CSS значениям. Вместо строк значения представляются в виде JS объектов для облегчения работы с манипулированием CSS.

Вместо element.style получить доступ к стилям можно через новое свойство .attributeStyleMap для элементов и свойство .styleMap для правил. Оба свойства возвращают объект StylePropertyMap.

// Element styles.
el.attributeStyleMap.set('opacity', 0.3);
typeof el.attributeStyleMap.get('opacity').value === 'number' // Yay, a number! // Stylesheet rules.
const stylesheet = document.styleSheets[0];
stylesheet.cssRules[0].styleMap.set('background', 'blue');

StylePropertyMaps – это объекты типа Map. Поэтому они поддерживают все ожидаемые вещи (get/set/keys/values/entries), что делает их гибкими в работе:

// All 3 of these are equivalent:
el.attributeStyleMap.set('opacity', 0.3);
el.attributeStyleMap.set('opacity', '0.3');
el.attributeStyleMap.set('opacity', CSS.number(0.3)); // see next section
// el.attributeStyleMap.get('opacity').value === 0.3 // StylePropertyMaps are iterable.
for (const [prop, val] of el.attributeStyleMap) { console.log(prop, val.value);
}
// → opacity, 0.3 el.attributeStyleMap.has('opacity') // true el.attributeStyleMap.delete('opacity') // remove opacity. el.attributeStyleMap.clear(); // remove all styles.

Обратите внимание, что во втором примере opacity присваивается строка ‘0.3’, но позже при считывании свойства возвращается число.

Если определенное CSS свойство поддерживает числа, Typed OM будет принимать строки, но возвращать всегда будет число! Аналогия между старой CSSOM и новой Typed OM – как .className выросло и получило свой API .classList.

Преимущества

Какую проблему CSS Typed OM пытается решить? Взглянув на примеры сверху (и на остальные примеры в статье), вы можете сказать, что у CSS Typed OM больше кода, чем у старой объектной модели. Соглашусь!

Прежде чем списать Typed OM взгляните на ключевые возможности:

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

el.style.opacity += 0.1;
el.style.opacity === '0.30.1' // dragons!

Арифметические операции и преобразование единиц измерения. Конвертируйте абсолютные единицы измерения длины (px -> cm) и выполняйте базовые математические операции.

Зажим и округление значений. Typed OM округляет и/или зажимает значения, чтобы они находились в доступном диапазоне для свойства.

Улучшенная производительность. Браузер выполняет меньше действий по сериализации и десериализации строковых значений. Теперь движок использует похожие понятия CSS значений в JS и С++. Tab Akins показал ранние тесты скорости, в которых видно, что Typed OM ~30% быстрее в операциях в секунду по сравнению со старой CSSOM и строками. Это может быть важно для быстрой CSS анимации с использованием requestionAnimationFrame(). crbug.com/808933 отслеживает дополнительную работу по производительности в Blink.

Обработка ошибок. Новые методы парсинга открыли миру CSS обработку ошибок.

«Использовать верблюжий регистр в CSS именах или строки?» Больше не нужно гадать, находятся имена в верблюжьем регистре или это строки (например, el.style.backgroundColor или el.style[‘background-color’]). В Typed OM имена свойств CSS всегда являются строками, что совпадает с тем, как вы пишите CSS :)

Поддержка в браузерах и определение функций

В Chrome Typed OM появилась в 66 версии, в Firefox поддержка уже есть. Edge показал признаки поддержки, но еще не добавил его в панель инструментов.

Примечание: на данный момент в Chrome 66+ поддерживается только подмножество CSS свойств.

Для определения функций можно проверять, определена ли одна из числовых фабрик CSS.*:

if (window.CSS && CSS.number) { // Supports CSS Typed OM.
}

Основы API

Получение стилей

В Typed OM значения отделены от единиц измерения. При получении стиля возвращается CSSUnitValue, содержащий value и unit:

el.attributeStyleMap.set('margin-top', CSS.px(10));
// el.attributeStyleMap.set('margin-top', '10px'); // string arg also works.
el.attributeStyleMap.get('margin-top').value // 10
el.attributeStyleMap.get('margin-top').unit // 'px' // Use CSSKeyWorldValue for plain text values:
el.attributeStyleMap.set('display', new CSSKeywordValue('initial'));
el.attributeStyleMap.get('display').value // 'initial'
el.attributeStyleMap.get('display').unit // undefined

Вычисляемые стили

Вычисляемые стили переехали из API в window в новый метод computedStyleMap(): на HTMLElement:

Старая CSSOM

el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!

Новая Typed OM

el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5

Примечание: между window.getComputedStyle() и element.computedStyleMap() есть один минус – первый возвращает разрешенное значение, а второй возвращает вычисленное. Например, Typed OM сохраняет процентные значения (width: 50%), а CSSOM разрешает их в значения длины (например, width: 200px).

Зажим/округление значений

Одна из классных функций в новой объектной модели – это автоматический зажим и/или округление вычисляемых значений стилей. Например, вы пытаетесь задать opacity за пределами диапазона [0,1]. Typed OM зажимает значение до 1 при вычислении стилей:

el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3 // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.

Точно так же z-index:15.4 округляет значение до 15, поэтому значение остается целочисленным.

el.attributeStyleMap.set('z-index', CSS.number(15.4));
el.attributeStyleMap.get('z-index').value === 15.4 // val not rounded.
el.computedStyleMap().get('z-index').value === 15 // computed style is rounded.

Числовые значения CSS

Числа представлены двумя типами объектов CSSNumericValue в Typed OM:

CSSUnitValue – значения, содержащие один тип единиц измерения (например, «42px»).

CSSMathValue – значения, содержащие более одного значения/единицы, например, математическое выражение («calc(56em + 10%)»).

Значения единиц измерения

Простые числовые значения («50%») представлены объектами CSSUnitValue. Объекты можно создавать напрямую (new CSSUnitValue(10, ‘px’)), но чаще всего вы будете использовать методы фабрики CSS.*:

const {value, unit} = CSS.number('10');
// value === 10, unit === 'number' const {value, unit} = CSS.px(42);
// value === 42, unit === 'px' const {value, unit} = CSS.vw('100');
// value === 100, unit === 'vw' const {value, unit} = CSS.percent('10');
// value === 10, unit === 'percent' const {value, unit} = CSS.deg(45);
// value === 45, unit === 'deg' const {value, unit} = CSS.ms(300);
// value === 300, unit === 'ms'

Примечание: как показано в примерах, в методы можно передавать Number или String в виде числа.

Полный список методов CSS.* смотрите в спецификации.

Математические значения

Объекты CSSMathValue представляют математические выражения и обычно содержат более одного значения/единицы. Общий пример – создание CSS выражения calc(), но есть и методы для всех CSS функций: calc(), min(), max().

new CSSMathSum(CSS.vw(100), CSS.px(-10)).toString(); // "calc(100vw + -10px)" new CSSMathNegate(CSS.px(42)).toString() // "calc(-42px)" new CSSMathInvert(CSS.s(10)).toString() // "calc(1 / 10s)" new CSSMathProduct(CSS.deg(90), CSS.number(Math.PI/180)).toString();
// "calc(90deg * 0.0174533)" new CSSMathMin(CSS.percent(80), CSS.px(12)).toString(); // "min(80%, 12px)" new CSSMathMax(CSS.percent(80), CSS.px(12)).toString(); // "max(80%, 12px)"

Вложенные выражения

Создание более сложных значений с помощью математических функций стало немного запутаннее. Ниже представлено несколько примеров. Я добавил отступы, чтобы код было легче читать.

calc(1px — 2 * 3em) теперь выглядит так:

new CSSMathSum( CSS.px(1), new CSSMathNegate( new CSSMathProduct(2, CSS.em(3)) )
);

calc(1px + 2px + 3px) превратился в:

new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));

calc(calc(1px + 2px) + 3px) превратился в:

new CSSMathSum( new CSSMathSum(CSS.px(1), CSS.px(2)), CSS.px(3)
);

Арифметические операции

Одна из полезнейших функций Typed OM заключается в том, что вы можете выполнять математические операции над объектами CSSUnitValue.

Базовые операции

Поддерживаются базовые операции (add/sub/mul/div/min/max):

CSS.deg(45).mul(2) // {value: 90, unit: "deg"} CSS.percent(50).max(CSS.vw(50)).toString() // "max(50%, 50vw)" // Can Pass CSSUnitValue:
CSS.px(1).add(CSS.px(2)) // {value: 3, unit: "px"} // multiple values:
CSS.s(1).sub(CSS.ms(200), CSS.ms(300)).toString() // "calc(1s + -200ms + -300ms)" // or pass a `CSSMathSum`:
const sum = new CSSMathSum(CSS.percent(100), CSS.px(20)));
CSS.vw(100).add(sum).toString() // "calc(100vw + (100% + 20px))"

Преобразование

Единицы абсолютной длины можно конвертировать в другие единицы измерения длины:

// Convert px to other absolute/physical lengths.
el.attributeStyleMap.set('width', '500px');
const width = el.attributeStyleMap.get('width');
width.to('mm'); // CSSUnitValue {value: 132.29166666666669, unit: "mm"}
width.to('cm'); // CSSUnitValue {value: 13.229166666666668, unit: "cm"}
width.to('in'); // CSSUnitValue {value: 5.208333333333333, unit: "in"} CSS.deg(200).to('rad').value // 3.49066...
CSS.s(2).to('ms').value // 2000

Равенство

const width = CSS.px(200);
CSS.px(200).equals(width) // true const rads = CSS.deg(180).to('rad');
CSS.deg(180).equals(rads.to('deg')) // true

Значения CSS transform

CSS transforms создаются с помощью CSSTransformValue и передаваемого массива значений трансформации (например, CSSRotate, CSScale, CSSSkew, CSSSkewX, CSSSkewY). Например, вы хотите воссоздать этот CSS:

transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);

В переводе на Typed OM:

const transform = new CSSTransformValue([ new CSSRotate(CSS.deg(45)), new CSSScale(CSS.number(0.5), CSS.number(0.5)), new CSSTranslate(CSS.px(10), CSS.px(10), CSS.px(10))
]);

Помимо того, что код длиннее, у CSSTransformValue есть несколько крутых функций. У него есть Булево свойство для различения 2D и 3D трансформаций, а также метод .toMatrix() для возврата представления DOMMatrix трансформации:

new CSSTranslate(CSS.px(10), CSS.px(10)).is2D // true
new CSSTranslate(CSS.px(10), CSS.px(10), CSS.px(10)).is2D // false
new CSSTranslate(CSS.px(10), CSS.px(10)).toMatrix() // DOMMatrix

Пример: анимированный куб

Рассмотрим практический пример использования трансформаций. Для анимации куба задействуем JS и CSS трансформации.

const rotate = new CSSRotate(0, 0, 1, CSS.deg(0));
const transform = new CSSTransformValue([rotate]); const box = document.querySelector('#box');
box.attributeStyleMap.set('transform', transform); (function draw() { requestAnimationFrame(draw); transform[0].angle.value += 5; // Update the transform's angle. // rotate.angle.value += 5; // Or, update the CSSRotate object directly. box.attributeStyleMap.set('transform', transform); // commit it.
})();

Обратите внимание:

Числовые значения означают, что мы можем напрямую увеличивать по инкременту угол с помощью вычислений!

Чтобы не трогать DOM или считывать значение каждого кадра (например, no box.style.transform=`rotate(0,0,1,${newAngle}deg)`), анимация обновляется через объект данных CSSTransformValue, повышая производительность.

Если ваш браузер поддерживает Typed OM, ниже будет красный куб. Куб начинает вращаться при наведении мыши. Анимация управляется CSS Typed OM!

Значения пользовательских свойств CSS

CSS var() стал объектом CSSVariableReferenceValue в Typed OM. Их значения парсятся в CSSUnparsedValue, потому что они могут принимать любой тип (px, %, em, rgba() и т.д.).

const foo = new CSSVariableReferenceValue('--foo');
// foo.variable === '--foo' // Fallback values:
const padding = new CSSVariableReferenceValue( '--default-padding', new CSSUnparsedValue(['8px']));
// padding.variable === '--default-padding'
// padding.fallback instanceof CSSUnparsedValue === true
// padding.fallback[0] === '8px'

Получить значение пользовательского свойства можно следующим образом:

<style> body { --foo: 10px; }
</style>
<script> const styles = document.querySelector('style'); const foo = styles.sheet.cssRules[0].styleMap.get('--foo').trim(); console.log(CSSNumericValue.parse(foo).value); // 10
</script>

Значения положения

CSS свойства, принимающие положение x/y, разделенное запятой, например, object-position представлены через объекты CSSPositionValue.

const position = new CSSPositionValue(CSS.px(5), CSS.px(10));
el.attributeStyleMap.set('object-position', position); console.log(position.x.value, position.y.value);
// → 5, 10

Парсинг значений

Typed OM представляет методы парсинга в веб-платформу! Вы наконец-то можете программно парсить CSS значения, прежде чем их использовать! Эта новая возможность сэкономит время на поиск ранних багов и искаженного CSS.

Парсинг всего стиля:

const css = CSSStyleValue.parse( 'transform', 'translate3d(10px,10px,0) scale(0.5)');
// → css instanceof CSSTransformValue === true
// → css.toString() === 'translate3d(10px, 10px, 0) scale(0.5)'

Парсинг значений в CSSUnitValue:

CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'} // But it's easier to use the factory functions:
CSS.px(42.0) // '42px'

Обработка ошибок

Пример – проверьте, что выдаст парсер CSS с этим значением transform:

try { const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)'); // use css
} catch (err) { console.err(err);
}

Заключение

Приятно наконец-то получить обновленную объектную модель для CSS. Мне всегда было неудобно работать со строками. CSS Typed OM API немного длиннее, но, надеюсь, так будет меньше багов, а код станет быстрее.

Автор: Eric Bidelman

Источник: https://developers.google.com/

Редакция: Команда webformyself.