Типы данных в CSS

София Валитова, ВКонтакте

Предыстория

CSS иногда работает не так, как я ожидаю

            height: calc(3px+4px)
            height: calc(3px - 4px)
            height: calc(25% - 0)
        

Предыстория

CSS иногда работает не так, как я ожидаю

            height: calc(3px+4px)
            height: calc(3px - 4px)       // => 0px
            height: calc(25% - 0)
        

Предыстория

Посмотрим в спецификацию и пойдем гуглить статьи с примерами

Спецификация CSS Values

Где в CSS данные?

Спецификация CSS Syntax

            <style-rule> ::=
              <selectors-list> { <properties-list> }
            <selectors-list> ::=
              <selector>[':' <pseudo-class>] ['::' <pseudo-element>]
              [',' <selectors-list>]
            <properties-list> ::=
              [<property> ':' <value>] [';' <properties-list>]
        

Немного формальной грамматики

Простой пример

Попробуем описать грамматику отрицательных чисел от -1 до -9:

            <neg_num> ::= "-" <num>
            <num> ::= "1" | "2" | "3" | "4" | "5" |
                  | "6" | "7" | "8" | "9"
        

Простой пример

Попробуем описать грамматику отрицательных чисел от -1 до -9:

            <neg_num> ::= "-" <num>
            <num> ::= "1" | "2" | "3" | "4" | "5" |
                  | "6" | "7" | "8" | "9"
        

Простой пример

Попробуем описать грамматику отрицательных чисел от -1 до -9:

            <neg_num> ::= "-" <num>
            <num> ::= "1" | "2" | "3" | "4" | "5" |
                  | "6" | "7" | "8" | "9"
        

Сложение отрицательных чисел:

                <add_expr> ::= <neg_num> "+" "(" <neg_num> ")"
                
            

Простой пример

Попробуем описать грамматику отрицательных чисел от -1 до -9:

            <neg_num> ::= "-" <num>
            <num> ::= "1" | "2" | "3" | "4" | "5" |
                  | "6" | "7" | "8" | "9"
        

Сложение отрицательных чисел:

                <add_expr> ::= <neg_num> "+" "(" <neg_num> ")"
                
            

Простой пример

Попробуем описать грамматику отрицательных чисел от -1 до -9:

            <neg_num> ::= "-" <num>
            <num> ::= "1" | "2" | "3" | "4" | "5" |
                  | "6" | "7" | "8" | "9"
        

Сложение отрицательных чисел:

                <add_expr> ::= <neg_num> "+" "(" <neg_num> ")"
                
            

Форма Бэкуса-Наура

"text" или 'text' терминал
<A> нетерминал
<A> ::= <B> <A> заменимо на <B>
<A> <B> конкатенация
<A> | <B> один из элементов
integer имя класса нетерминалов, описывается не через BNF
            <expression> ::= <add_expr>
            <add_expr> ::= <mul_expr> '+' <add_expr>
                | <mul_expr> '-' <add_expr>
                | <mul_expr>
            <mul_expr> ::= integer '*' <mul_expr>
                | integer '/' <mul_expr>
                | integer '%' <mul_expr>
                | integer
            integer ::= [0-9]+      <- класс нетерминалов
        
            <expression> ::= <add_expr>
            <add_expr> ::= <mul_expr> '+' <add_expr>
                | <mul_expr> '-' <add_expr>
                | <mul_expr>
            <mul_expr> ::= integer '*' <mul_expr>
                | integer '/' <mul_expr>
                | integer '%' <mul_expr>
                | integer
            integer ::= [0-9]+
        
            <expression> ::= <add_expr>
            <add_expr> ::= <mul_expr> '+' <add_expr>
                | <mul_expr> '-' <add_expr>
                | <mul_expr>
            <mul_expr> ::= integer '*' <mul_expr>
                | integer '/' <mul_expr>
                | integer '%' <mul_expr>
                | integer
            integer ::= [0-9]+
        
            <expression> ::= <add_expr>
            <add_expr> ::= <mul_expr> '+' <add_expr>
                | <mul_expr> '-' <add_expr>
                | <mul_expr>
            <mul_expr> ::= integer '*' <mul_expr>
                | integer '/' <mul_expr>
                | integer '%' <mul_expr>
                | integer
            integer ::= [0-9]+
        
            <expression> ::= <add_expr>
            <add_expr> ::= <mul_expr> '+' <add_expr>
                | <mul_expr> '-' <add_expr>
                | <mul_expr>
            <mul_expr> ::= integer '*' <mul_expr>
                | integer '/' <mul_expr>
                | integer '%' <mul_expr>
                | integer
            integer ::= [0-9]+
        
            <expression> ::= <add_expr>
            <add_expr> ::= <mul_expr> '+' <add_expr>
                | <mul_expr> '-' <add_expr>
                | <mul_expr>
            <mul_expr> ::= integer '*' <mul_expr>
                | integer '/' <mul_expr>
                | integer '%' <mul_expr>
                | integer
            integer ::= [0-9]+
        
            <expression> ::= <add_expr>
            <add_expr> ::= <mul_expr> [ ('+' | '-') <add_expr> ]
            <mul_expr> ::= integer [ ('*' | '/' | '%') <mul_expr> ]
            integer ::= [0-9]+
        
                <mul_expr> ::= integer '*' <mul_expr>
                    | integer '/' <mul_expr>
                    | integer '%' <mul_expr>
                    | integer
            
                <add_expr> ::= <mul_expr> '+' <add_expr>
                    | <mul_expr> '-' <add_expr>
                    | <mul_expr>
            

Расширенная форма Бэкуса-Наура

[<A>] условное вхождение
(<A> | <B>) <C> группировка
{<A>} повторение элемента (0 или более раз)
            <style-rule> ::=
              <selectors-list> '{' <properties-list> '}'
            <selectors-list> ::=
              <selector>[':' <pseudo-class>] ['::' <pseudo-element>]
              [',' <selectors-list>]
            <properties-list> ::=
              [<property> ':' <value>] [';' <properties-list>]
        
            <style-rule> ::=
              <selectors-list> '{' <properties-list> '}'
        
            .class#id { 
                height: initial;
                width: 100%;
            }
        
            <style-rule> ::=
              <selectors-list> '{' <properties-list> '}'
        
            .class#id }
                height: initial;
                width: 100%;
            }
        
            <style-rule> ::=
              <selectors-list> '{' <properties-list> '}'
        
            .class#id { 
                height: initial;
                width: 100%;
            }
        
            <style-rule> ::=
              <selectors-list> '{' <properties-list> '}'
        
            .class#id { 
                height: initial;
                width: 100%;
            }
        
            <properties-list> ::=
              [<property> ':' <value>] [';' <properties-list>]
        
            .class#id {}
        
            <properties-list> ::=
              [<property> ':' <value>] [';' <properties-list>]
        
            .class#id { 
                height: initial
            }
        
            <properties-list> ::=
              [<property> ':' <value>] [';' <properties-list>]
        
            .class#id { 
                height: initial
            }
        
            <properties-list> ::=
              [<property> ':' <value>] [';' <properties-list>]
        
            .class#id { 
                height: initial
            }
        
            <properties-list> ::=
              [<property> ':' <value>] [';' <properties-list>]
        
            .class#id { 
                height: initial
            }
        
            <properties-list> ::=
              [<property> ':' <value>] [';' <properties-list>]
        
            .class#id { 
                height: initial;
                width: 100%
            }
        
            .class { 
                height: initial;
                width: 100%;
                border-width: 2px medium 4px;
                box-shadow: rgba(0,0,0,0.4) 10px 10px inset;
                margin: 0 -7px 0 2px;
                transition: opacity .2s ease, visibility .2s ease;
            }
        
            margin: 0 -7px 0 2px;
        
            <margin> ::= "auto"
               | <length> 
               | <length> {" "} <length>
               | <length> {" "} <length> {" "} <length>
               | <length> {" "} <length> {" "} <length> {" "} <length>
        

Value Definition Syntax

  1. ключевые слова
                        initial
                    
  2. базовые типы данных
                         <length>
                    
  3. сложные типы данных
                         <'background-attachment'>
                    
[<A> | <B>] <C> группировка
<A> || <B> || <C> один или более из группы в заданном порядке
<A> && <B> && <C> один или более из группы в любом порядке
<A>* ноль или более раз
<A>+ один или более раз
<A>? необязательный
<A> {n} А повторяется n раз
<A> {n, m} А повторяется не меньше n раз и не больше m раз
<A># один или более раз, разделенные запятыми
            <'margin'> ::= "auto" | <length>{1, 4} 
              
            <'line-height'> ::= 
               normal | <number> | <length> | <percentage>
              
            <'font-family'> = 
              [ <family-name> | <generic-family> ]#
        
            <'border-width'> = 
              [ <length> | thick | medium | thin ]{1,4}
        
            border-width: 3px;
            border-width: thick;
            border-width: medium thick;
            border-width: 8em thick;
        
            <'text-shadow'> = 
              [ inset? [ <length>{2,4} <color>? ]# ] | none
        
            text-shadow: none;
            text-shadow: 1px 1px 2px black;
            text-shadow: inset 1px 1px 2px black;
            text-shadow: inset 1px 1px 2px black, 
0 0 1em red, 0 3vw green;

Посмотрим на проблему еще раз

Посмотрим на проблему еще раз

CSS иногда работает не так, как я ожидаю

            height: calc(3px+4px)
            height: calc(3px - 4px)       // => 0px
            height: calc(25% - 0)
        

Вокруг + и - должны быть пробелы

            calc(50%-2em);
        
            calc(50% - 2em);  // => <length> '-' <length>
            calc(50% -2em);   // => <length> <length>
            calc(50% - -2em);
            calc(50% - (-2em));
        

Посмотрим на проблему еще раз

CSS иногда работает не так, как я ожидаю

            height: calc(3px+4px)
            height: calc(3px - 4px)       // => 0px
            height: calc(25% - 0)
        

Calc(). Type Checking

  1. Нельзя использовать в одном выражении <length> и <time>

Calc(). Type Checking

Нельзя использовать в одном выражении несовместимые типы

            calc(5px + 10s)  // => <length> '+' <time>
            calc(5deg / 5em)  // => <angle> '/' <length>
            calc(5px - 5)  // => <length> '-' <number-token>
            calc(5px - 0)  // => <length> '-' <number-token>
        

Посмотрим на проблему еще раз

CSS иногда работает не так, как я ожидаю

            height: calc(3px+4px)
            height: calc(3px - 4px)       // => 0px
            height: calc(25% - 0)
        

Похожая проблема

            height: -12px   
            height: calc(-12px)  // => 0px
        

Проверка соответствия значения свойства его грамматике

CSS Type Checking

Проверка соответствия значения свойства его грамматике

CSS Type Checking

Проверка соответствия значения свойства его грамматике

CSS Type Checking

Проверка соответствия значения свойства его грамматике

Грамматика свойства:
                height : <length>[0,∞] | auto | <percantage> 
                
            

* Negative values for 'height' are illegal.

CSS Type Checking

            height: -12px        // => auto
            height: calc(-12px)  // => 0px
        

Проводится перед каскадом

CSS Type Checking

            height: -12px        // => auto
            height: calc(-12px)  // => 0px
        
            height: -12s         // => auto
            height: calc(-12s)   // => auto
        

Проверка финального типа, но не диапазона

Calc(). Range Checking

            height: calc(-12px);
                // => 0px при обращении 
        

Calc(). Range Checking

            height: calc(-12px);
                // => 0px при обращении 
        

Разные ситуации:

                    height: -12px;
                        // property syntax check
                
                    height: calc(-12px);
                        // calc range check
                

Calc(). Range Checking

                height: 100px; // => 100px
            
                    height: 50%; // => 50px 
                
                        height: inherit; // 25px 
                    

Calc(). Range Checking

                height: 100px; // => 100px
            
                    height: calc(50% - 25px); // => 25px 
                
                        height: inherit; // => calc(50% - 25px) => 0px  
                    

Посмотрим на проблему еще раз

CSS иногда работает не так, как я ожидаю

            height: calc(3px+4px)
            height: calc(3px - 4px)       // => 0px
            height: calc(25% - 0)
        

Итого:

Взгляд в будущее

            #el {
              background-color: red;
              animation: anim infinite 3s;
            }
            @keyframes anim {
              50% { background-color: blue; }
            }
        
            #el {
              --color-stop: red;
              background: var(--color-stop);
              animation: anim infinite 3s;
            }
            @keyframes anim {
              50% { --color-stop: blue; }
            }
        

Почему?

Потому что изначально CSS-переменные не типизированы

            div {
              --name: red;
              --name: 11px;
            
        
            #el {
              --color-stop: red;
              margin: 50px;
              animation: anim infinite 3s;
            }
            @keyframes anim {
              50% { --color-stop: 10px;
                   margin: var(--color-stop); }
            }
        

Почему?

Браузер не знает, как построить переход от одного значения к другому

            red  =>  ???  =>  blue
            red  =>  ???  =>  10px
        

Типизация переменной

            CSS.registerProperty({
              name: '--color-stop',
              syntax: '<color>',
              inherits: false,
              initialValue: 'transparent'
            })
        
            #el {
              --color-stop: red;
              background: var(--color-stop);
              animation: anim infinite 3s;
            }
            @keyframes anim {
              50% { --color-stop: blue; }
            }
        

CSS Typed OM

JS =>
=> CSS

CSS Typed OM

            const height = CSS.px(2);
            elem.attributeStyleMap.set("height", height);
            
        

CSS Typed OM

Итого

Источники

Почтовые рассылки W3C

София Валитова из ВКонтакте

Презентация сделана с помощью Shower