- PVSM.RU - https://www.pvsm.ru -

CSS в JavaScript: будущее компонентных стилей

CSS в JavaScript: будущее компонентных стилей - 1

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

Чтобы получить больше информации о проблемах CSS, решаемых в JavaScript, вы можете посмотреть презентацию «React CSS в JS» (React CSS in JS [1]), а для того чтобы изучить улучшение производительности с помощью Aphrodite, прочитайте статью Inline CSS at Khan Academy: Aphrodite [2]. Если же вы хотите узнать больше о лучших практиках CSS в JavaScript, ознакомьтесь с руководством Airbnb (Airbnb’s styleguide [3]).

Здесь речь пойдет об использовании встроенных стилей JavaScript для создания компонентов, позволяющих решить основные проблемы дизайна, о которых я рассказывал ранее в статье «Прежде чем осваивать дизайн, необходимо ознакомиться с основами» (Before you can master design, you must first master the fundamentals [4]).

Мотивирующий пример

Начнем с простого примера: создание и стилизация кнопки.
Как правило, компонент и связанные с ним стили находятся в одном файле: Button и ButtonStyles. Причина в том, что они относятся к одной и той же вещи — к представлению (view). Однако в примере я разбил код на несколько составляющих, чтобы сделать его более наглядным.

Рассмотрим элемент кнопки:

...

function Button(props) {
  return (
    <input
      type="button"
      className={css(styles.button)}
      value={props.text}
    />
  );
}

Ничего необычного — просто React-компонент. Свойство className — вот где Aphrodite вступает в игру. Функция CSS принимает объект styles и преобразует его в CSS. Объект styles создается с помощью функции StyleSheet.create Aphrodite ({...}). Вы можете посмотреть результат StyleSheet.create ({...}) на странице Aphrodite (Aphrodite playground [5]).

Ниже приведена таблица стилей кнопок:

...

const gradient = 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)';

const styles = StyleSheet.create({
  button: {
    background: gradient,
    borderRadius: '3px',
    border: 0,
    color: 'white',
    height: '48px',
    textTransform: 'uppercase',
    padding: '0 25px',
    boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .30)',
  },
});

Простая миграция и низкая кривая обучения — вот преимущества Aphrodite. Свойства border-radius преобразуются в borderRadius, а значения становятся строками. Псевдоселекторы, медиазапросы и определения шрифтов — все работает. Кроме того, автоматически добавляются вендорные префиксы.

В результате получаем:

image
Простая миграция и низкая кривая обучения — преимущества Aphrodite

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

Основной принцип № 1. Типографика

Начнем с фундаментальной основы дизайна — типографики. Для начала необходимо определить ее константы. В отличие от Sass или Less, константы для Aphrodite могут быть в файлах JavaScript или JSON.

Определение констант типографики

При создании констант используйте семантические имена для переменных. Например, для названия одного из размеров шрифта необходимо имя, описывающее его роль, наподобие displayLarge, а не h2. Аналогично, чтобы обозначить одно из значений жирности, берите название вроде semibold, описывающее эффект, а не значение 600.

export const fontSize = {
  // heading
  displayLarge: '32px',
  displayMedium: '26px',
  displaySmall: '20px',
  heading: '18px',
  subheading: '16px',

  // body
  body: '17px',
  caption: '15px',
};

export const fontWeight = {
  bold: 700,
  semibold: 600,
  normal: 400,
  light: 200,
};

export const tagMapping = {
  h1: 'displayLarge',
  h2: 'displayMedium',
  h3: 'displaySmall',
  h4: 'heading',
  h5: 'subheading',
};

export const lineHeight = {
  // heading
  displayLarge: '48px',
  displayMedium: '48px',
  displaySmall: '24px',
  heading: '24px',
  subheading: '24px',

  // body
  body: '24px',
  caption: '24px',
};

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

Более подробно о вертикальном ритме вы можете прочитать в статье «Почему вертикальный ритм — это важная практика типографики?» (Why is Vertical Rhythm an Important Typography Practice? [6]).

image
Используйте калькулятор для определения высоты линии

Существует целая наука о выборе значений для строк и размеров шрифта. Для создания потенциальных размерных вариантов могут использоваться математические соотношения. Несколько недель назад я опубликовал статью «Типографика может создать или разрушить дизайн: выбор типа» (Typography can make or break your design: a process for choosing type [7]), в которой подробно изложена методология. Для определения размеров шрифтов используйте модульную шкалу (Modular Scale [8]), а чтобы установить высоту линий, можно применять калькулятор вертикального ритма (vertical rhythm calculator [9]).

Определение компонента заголовка

После того как мы определили константы типографики, необходимо создать компонент, который будет использовать эти значения. Цель такого компонента — обеспечить согласованность в разработке и реализации заголовков кодовой базы.

import React, { PropTypes } from 'react';
import { StyleSheet, css } from 'aphrodite/no-important';
import { tagMapping, fontSize, fontWeight, lineHeight } from '../styles/base/typography';

function Heading(props) {
  const { children, tag: Tag } = props;
  return <Tag className={css(styles[tagMapping[Tag]])}>{children}</Tag>;
}

export default Heading;

export const styles = StyleSheet.create({
  displayLarge: {
    fontSize: fontSize.displayLarge,
    fontWeight: fontWeight.bold,
    lineHeight: lineHeight.displayLarge,
  },
  displayMedium: {
    fontSize: fontSize.displayMedium,
    fontWeight: fontWeight.normal,
    lineHeight: lineHeight.displayLarge,
  },
  displaySmall: {
    fontSize: fontSize.displaySmall,
    fontWeight: fontWeight.bold,
    lineHeight: lineHeight.displaySmall,
  },
  heading: {
    fontSize: fontSize.heading,
    fontWeight: fontWeight.bold,
    lineHeight: lineHeight.heading,
  },
  subheading: {
    fontSize: fontSize.subheading,
    fontWeight: fontWeight.bold,
    lineHeight: lineHeight.subheading,
  },
});

Компонент Heading — это функция, которая принимает тег как свойство и возвращает его со связанным стилем. Это стало возможным благодаря тому, что ранее мы определили сопоставления тегов в файле констант.

...
export const tagMapping = {
  h1: 'displayLarge',
  h2: 'displayMedium',
  h3: 'displaySmall',
  h4: 'heading',
  h5: 'subheading',
};

В нижней части файла компонента мы определяем объект styles. Здесь используются константы типографики.

export const styles = StyleSheet.create({
  displayLarge: {
    fontSize: fontSize.displayLarge,
    fontWeight: fontWeight.bold,
    lineHeight: lineHeight.displayLarge,
  },

  ...
});

А компонент Heading будет применяться следующим образом:

function Parent() {
  return (
    <Heading tag="h2">Hello World</Heading>
  );
}

При таком подходе мы снижаем вероятность возникновения неожиданной изменчивости в системе типов. Устраняя потребность в глобальных стилях и стандартизируя заголовки кодовой базы, мы избегаем проблем с различными размерами шрифтов. К тому же подход, который мы использовали при построении компонента Heading, можно применить и для построения компонента Text основного тела.

Основной принцип № 2. Интервал

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

Определение интервальных констант

Чтобы определить интервальные константы для полей между элементами, можно прибегнуть к математическому подходу. Используя константу spacingFactor, мы можем сгенерировать набор расстояний на основе общего коэффициента. Такой подход обеспечивает логическое и согласованное расстояние между элементами.

const spacingFactor = 8;
export const spacing = {
  space0: `${spacingFactor / 2}px`,  // 4
  space1: `${spacingFactor}px`,      // 8
  space2: `${spacingFactor * 2}px`,  // 16
  space3: `${spacingFactor * 3}px`,  // 24
  space4: `${spacingFactor * 4}px`,  // 32
  space5: `${spacingFactor * 5}px`,  // 40
  space6: `${spacingFactor * 6}px`,  // 48

  space8: `${spacingFactor * 8}px`,  // 64
  space9: `${spacingFactor * 9}px`,  // 72
  space13: `${spacingFactor * 13}px`, // 104
};

В приведенном выше примере используется линейный масштаб один к тринадцати. Однако вы можете экспериментировать с разными шкалами и коэффициентами, поскольку в дизайне требуется применение разных масштабов, в зависимости от конечной цели, аудитории и устройств, на которых будет применяться программа. В качестве примера ниже приведены первые шесть расстояний, вычисленные с использованием золотого сечения, где spacingFactor равен восьми.

Золотое сечение(1:1.618)
8.0 x (1.618 ^ 0) = 8.000
8.0 x (1.618 ^ 1) = 12.94
8.0 x (1.618 ^ 2) = 20.94
8.0 x (1.618 ^ 3) = 33.89
8.0 x (1.618 ^ 4) = 54.82
8.0 x (1.618 ^ 5) = 88.71

Такой вид шкала интервала приобретет в коде. Я добавил вспомогательную функцию обработки вычислений и округления результата до ближайшего значения пикселя.

const spacingFactor = 8;
export const spacing = {
  space0: `${computeGoldenRatio(spacingFactor, 0)}px`,  // 8
  space1: `${computeGoldenRatio(spacingFactor, 1)}px`,  // 13
  space2: `${computeGoldenRatio(spacingFactor, 2)}px`,  // 21
  space3: `${computeGoldenRatio(spacingFactor, 3)}px`,  // 34
  space4: `${computeGoldenRatio(spacingFactor, 4)}px`,  // 55
  space5: `${computeGoldenRatio(spacingFactor, 5)}px`,  // 89
};

function computeGoldenRatio(spacingFactor, exp) {
  return Math.round(spacingFactor * Math.pow(1.618, exp));
}

После определения интервальных констант мы можем использовать их для добавления полей к элементам дизайна. Один из вариантов — это импортировать промежуточные константы и применять их в компонентах.

Например, добавим marginBottom к компоненту Button.

import { spacing } from '../styles/base/spacing';

...

const styles = StyleSheet.create({
  button: {
    marginBottom: spacing.space4, // adding margin using spacing constant
    ...
  },
});

Это работает в большинстве случаев. Однако что произойдет, если мы решим изменить свойство кнопки marginBottom в зависимости от ее расположения?

Один из способов получения переменных полей — переопределить стиль поля из потребляющего родительского компонента. Также можно создать компонент Spacing для управления вертикальными полями элементов.

import React, { PropTypes } from 'react';
import { spacing } from '../../base/spacing';

function getSpacingSize(size) {
  return `space${size}`;
}

function Spacing(props) {
  return (
    <div style={{ marginBottom: spacing[getSpacingSize(props.size)] }}>
      {props.children}
    </div>
  );
}

export default Spacing;

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

Это работает за счет таких компонентов, как кнопки, инпуты и карточки (cards), которые могут нуждаться в margin, заданном переменными. Например, для кнопки формы может потребоваться большее поле, чем для кнопки панели навигации.

Вы, наверное, заметили, что в приведенных примерах используется только marginBottom. Это связано с тем, что определение всех вертикальных полей в одном направлении позволяет избежать их сбрасывания, а также дает возможность отслеживать вертикальный ритм дизайна. Узнать об этом больше вы можете из статьи Гарри Роберта «Описание одностороннего поля» (Single-direction margin declarations [10]).

Также вы можете использовать интервальные константы, которые были определены как отступы.

import React, { PropTypes } from 'react';
import { StyleSheet, css } from 'aphrodite/no-important';
import { spacing } from '../../styles/base/spacing';

function Card(props) {
  return (
    <div className={css(styles.card)}>
      {props.children}
    </div>
  );
}

export default Card;

export const styles = StyleSheet.create({
  card: {
    padding: spacing.space4}, // using spacing constants as padding

    background: 'rgba(255, 255, 255, 1.0)',
    boxShadow: '0 3px 17px 2px rgba(0, 0, 0, .05)',
    borderRadius: '3px',
  },
});

С одинаковыми интервальными константами для полей и для отступов можно добиться большей визуальной согласованности в дизайне.

Результат выглядит следующим образом:

image
С одинаковыми интервальными константами для полей и для отступов можно добиться большей визуальной согласованности в дизайне

Теперь, когда у вас есть понимание CSS в JavaScript, можете смело экспериментировать. Попробуйте включить встроенные стили JavaScript в свой следующий проект. Думаю, вы по достоинству оцените возможность решать все проблемы стиля и представления (view), работая в одном контексте.

Автор: AloneCoder

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/256725

Ссылки в тексте:

[1] React CSS in JS: https://speakerdeck.com/vjeux/react-css-in-js

[2] Inline CSS at Khan Academy: Aphrodite: http://engineering.khanacademy.org/posts/aphrodite-inline-css.htm

[3] Airbnb’s styleguide: https://github.com/airbnb/javascript/tree/master/css-in-javascript

[4] Before you can master design, you must first master the fundamentals: https://medium.freecodecamp.com/before-you-can-master-design-you-must-first-master-the-fundamentals-1981a2af1fda

[5] Aphrodite playground: https://output.jsbin.com/qoseye?

[6] Why is Vertical Rhythm an Important Typography Practice?: https://zellwk.com/blog/why-vertical-rhythms/

[7] Typography can make or break your design: a process for choosing type: https://medium.freecodecamp.com/typography-can-make-your-design-or-break-it-7be710aadcfe

[8] Modular Scale: http://www.modularscale.com/

[9] vertical rhythm calculator: https://drewish.com/tools/vertical-rhythm/

[10] Single-direction margin declarations: https://csswizardry.com/2012/06/single-direction-margin-declarations/

[11] Источник: https://habrahabr.ru/post/329710/