Web 開発の世界では、CSS はユーザー インターフェースを美しく機能的にするための重要な要素です。
しかし、Web アプリケーションの複雑さが増すにつれて、CSS 管理はますます困難なタスクになってきています。スタイルの競合、パフォーマンスの低下、メンテナンスの困難は、多くの開発者にとって懸念事項です。
これらの問題はプロジェクトの進行を妨げていませんか? (画像出典)
この記事では、これらの問題を解決するための新しいアプローチ、特に JS の CSSについて詳しく説明します。
CSS の歴史的背景から始まり、現代のスタイリング方法から将来のデザイン システムまで幅広いトピックをカバーしています。
記事の構成は次のとおりです:
特に、この記事では SCALE CSS 手法と StyleStack と呼ばれる新しい概念を紹介し、これらに基づいた明朝体プロジェクトを提案します。 CSS フレンドリーでスケーラブルな CSS を JS に実装することを目的としています。
この記事の最終的な目的は、より良いスタイル ソリューションの可能性を開発者、デザイナー、その他の Web プロジェクト関係者に提示することです。
さて、本文ではJSにおけるCSSの世界をさらに掘り下げていきましょう。長い道のりではありますが、新たなインスピレーションと挑戦の機会を提供できれば幸いです。
JS の CSS は、JavaScript (または TypeScript) コード内に CSS スタイルを直接記述できるようにする手法です。
個別の CSS ファイルを作成する代わりに、JavaScript ファイル内のコンポーネントと一緒にスタイルを定義できます。
/** @jsxImportSource @emotion/react */ import { css } from "@emotion/react"; const buttonStyles = (primary) => css({ backgroundColor: primary ? "blue" : "white", color: primary ? "white" : "black", fontSize: "1em", padding: "0.25em 1em", border: "2px solid blue", borderRadius: "3px", cursor: "pointer", }); function Button({ primary, children }) { return ( <button css={buttonStyles(primary)}> {children} </button> ); } function App() { return ( <div> <Button>Normal Button</Button> <Button primary>Primary Button</Button> </div> ); }
JavaScript に統合できると確かに便利そうですね?
JS の CSS は、Facebook 開発者である Vjeux による「React: CSS in JS – NationJS」というタイトルのプレゼンテーションで紹介されました。
CSS-in-JS が解決しようとした問題は次のとおりです:
具体的にはどのような問題がありますか?
そして、JS の CSS はそれをどのように解決するのでしょうか?
次の表に整理しました:
Problem | Solution | |
---|---|---|
Global namespace | Need unique class names that are not duplicated as all styles are declared globally | Use Local values as default - Creating unique class names - Dynamic styling |
Implicit Dependencies | The difficulty of managing dependencies between CSS and JS - Side effect: CSS is applied globally, so it still works if another file is already using that CSS - Difficulty in call automation: It's not easy to statically analyze and automate CSS file calls, so developers have to manage them directly |
Using the module system of JS |
Dead Code Elimination | Difficulty in removing unnecessary CSS during the process of adding, changing, or deleting features | Utilize the optimization features of the bundler |
Minification | Dependencies should be identified and reduced | As dependencies are identified, it becomes easier to reduce them. |
Sharing Constants | Unable to share JS code and state values | Use JS values as they are, or utilize CSS Variables |
Non-deterministic Resolution | Style priority varies depending on the CSS loading order | - Specificity is automatically calculated and applied - Compose and use the final value |
Breaking Isolation | Difficulty in managing external modifications to CSS (encapsulation) | - Encapsulation based on components - Styling based on state - Prevent styles that break encapsulation, such as .selector > * |
But it's not a silverbullet, and it has its drawbacks.
Aside from the DevTools issue, it appears to be mostly a performance issue.
Of course, there are CSS in JS, which overcomes these issues by extracting the CSS and making it zero runtime, but there are some tradeoffs.
Here are two examples.
Therefore, pursuing zero(or near-zero) runtime in CSS-in-JS implementation methods creates a significant difference in terms of expressiveness and API.
Where did CSS come from?
Early web pages were composed only of HTML, with very limited styling options.
<p><font color="red">This text is red.</font></p> <p>This is <strong>emphasized</strong> text.</p> <p>This is <em>italicized</em> text.</p> <p>This is <u>underlined</u> text.</p> <p>This is <strike>strikethrough</strike> text.</p> <p>This is <big>big</big> text, and this is <small>small</small> text.</p> <p>H<sub>2</sub>O is the chemical formula for water.</p> <p>2<sup>3</sup> is 8.</p>
For example, the font tag could change color and size, but it couldn't adjust letter spacing, line height, margins, and so on.
You might think, "Why not just extend HTML tags?" However, it's difficult to create tags for all styling options, and when changing designs, you'd have to modify the HTML structure itself.
This deviates from HTML's original purpose as a document markup language and also means that it's hard to style dynamically.
If you want to change an underline to a strikethrough at runtime, you'd have to create a strike element, clone the inner elements, and then replace them.
const strikeElement = document.createElement("strike"); strikeElement.innerHTML = uElement.innerHTML; uElement.parentNode.replaceChild(strikeElement, uElement);
When separated by style, you only need to change the attributes.
element.style.textDecoration = "line-through";
If you convert to inline style, it would be as follows:
<p style="color: red;">This text is red.</p> <p>This is <span style="font-weight: bold;">bold</span> text.</p> <p>This is <span style="font-style: italic;">italic</span> text.</p> <p>This is <span style="text-decoration: underline;">underlined</span> text.</p> <p>This is <span style="text-decoration: line-through;">strikethrough</span> text.</p> <p>This is <span style="font-size: larger;">large</span> text, and this is <span style="font-size: smaller;">small</span> text.</p> <p>H<span style="vertical-align: sub; font-size: smaller;">2</span>O is the chemical formula for water.</p> <p>2<span style="vertical-align: super; font-size: smaller;">3</span> is 8.</p>
However, inline style must be written repeatedly every time.
That's why CSS, which styles using selectors and declarations, was introduced.
<p>This is the <strong>important part</strong> of this sentence.</p> <p>Hello! I want to <strong>emphasize this in red</strong></p> <p>In a new sentence, there is still an <strong>important part</strong>.</p> <style> strong { color: red; text-decoration: underline; } </style>
Since CSS is a method that applies multiple styles collectively, rules are needed to determine which style should take precedence when the target and style of CSS Rulesets overlap.
CSS was created with a feature called Cascade to address this issue. Cascade is a method of layering styles, starting with the simple ones and moving on to the more specific ones later. The idea was that it would be good to create a system where basic styles are first applied to the whole, and then increasingly specific styles are applied, in order to reduce repetitive work.
Therefore, CSS was designed to apply priorities differently according to the inherent specificity of CSS Rules, rather than the order in which they were written.
/* The following four codes produce the same result even if their order is changed. */ #id { color: red; } .class { color: green; } h1 { color: blue; } [href] { color: yellow; } /* Even if the order is changed, the result is the same as the above code. */ h1 { color: blue; } #id { color: red; } [href] { color: yellow; } .class { color: green; }
However, as CSS became more scalable, a problem arose..
Despite the advancements in CSS, issues related to scalability in CSS are still being discussed.
In addition to the issues raised by CSS in JS, several other obstacles exist in CSS.
These issues can be addressed as follows:
レイアウトの表現は CSS のもう 1 つのハードルであり、さまざまなプロパティ間の相互作用によってさらに複雑になります。
CSS は一見シンプルに見えるかもしれませんが、習得するのは簡単ではありません。単純な中央揃えでも多くの人が苦労していることはよく知られています(1、2)。 CSS は一見シンプルに見えますが、その奥深さと微妙な違いにより、見た目よりも難しくなる場合があります。
たとえば、CSS での表示には、ブロック、インライン、テーブル、フレックス、グリッドなど、さまざまなレイアウト モデルがあります。
次のプロパティを組み合わせて使用するときの複雑さを想像してみてください: ボックス モデル、レスポンシブ デザイン、フロート、位置決め、変換、書き込みモード、マスクなど。
プロジェクトの規模が大きくなるにつれて、DOM の位置決め、カスケード、特異性に関連する副作用により、作業はさらに困難になります。
レイアウトの問題は、適切に設計された CSS フレームワークを通じて対処する必要があり、前述したように、JS で CSS を使用してスタイルを分離すると、副作用を軽減できます。
ただし、このアプローチではすべての問題が完全に解決されるわけではありません。スタイルの分離は、各コンポーネントでのスタイル宣言の重複によるファイル サイズの増加や、アプリケーション全体での共通スタイルの一貫性維持の困難など、新たな副作用を引き起こす可能性があります。
これは、次に紹介する設計の組み合わせ爆発と一貫性の問題と直接衝突します。
今のところ、私たちはレイアウトに関する懸念を Bootstrap や Bulma などのフレームワークに委任し、管理面により重点を置くことができます。
CSS は本質的に、Web 開発でデザインを表現し実装するための強力なツールです。
UI/UX を作成する際には考慮すべき要素が多数あり、デザインで表現するには次の要素が重要です。
さまざまな条件下でさまざまなデザイン要素を正確に表現することは、大きな課題となります。
デバイス (電話、タブレット、ラップトップ、モニター、テレビ)、入力デバイス (キーボード、マウス、タッチ、音声)、ランドスケープ/ポートレート モード、ダーク/ライト テーマ、ハイ コントラスト モード、国際化 (言語) を考慮する必要があることを考慮してください。 、LTR/RTL) など。
さらに、ユーザー設定に基づいて異なる UI を表示する必要がある場合があります。
したがって、組み合わせ爆発は避けられず、それらを 1 つずつ手動で実装することは不可能です。 (画像出典)
代表的な例として、Firefox テーマのタブ バー レイアウトの定義とコンパイルを参照してください。
OS とユーザーのオプションのみを考慮しているにも関わらず、約 360 行のファイルのコンパイル結果は約 1400 行に達します。
結論としては、効果的な設計の実装には本質的にスケーラブルが必要であり、通常はプログラムまたは明確に定義されたルールセットを通じて管理される必要があります。
その結果、大規模な一貫した管理のための設計システムが誕生しました。
デザイン システムは、ビジュアル スタイルから UI パターン、コード実装に至るまで、デザインと開発のあらゆる側面をカバーする単一の信頼できる情報源として機能します。
Nielsen Norman Group によると、デザイン システムには次のものが含まれます。
デザイン システムは、機能、フォーム、アクセシビリティ、および カスタマイズをサポートし、デザイナーと開発者にとっての交差点として機能する必要があります。
しかし、デザイナーと開発者は考え方が異なり、異なる視点を持っています。
コンポーネントをレンズとして使用して、設計者と開発者の視点の違いを認識しましょう!!
デザイナーは、チェックボックス コントロールにどのアイコンを使用するかを決定する必要もあります。
デザイナーは形状に注目する傾向があり、開発者は機能に注目する傾向があります。
デザイナーにとって、ボタンは押したくなるような見た目であればボタンですが、開発者にとっては、押せる限りボタンです。
コンポーネントがより複雑であれば、設計者と開発者の間の溝はさらに広がる可能性があります。
ビジュアルオプション: プライマリ、アクセント、アウトライン、テキストのみなどの設定されたオプションに従って外観が変わります。
状態オプション: 状態とコンテキストに応じて外観が変化します
デザインの決定: コンポーネント構造、ビジュアル/状態オプション、ビジュアル属性 (色、タイポグラフィ、アイコンなど) などの値を決定します。
最終的な形式は、オプション、状態、コンテキストの組み合わせであり、前述の組み合わせ爆発が生じます。
これらのうち、Option は設計者の視点と一致しますが、State と Context は一致しません。
並列状態、階層、ガードなどを考慮して状態圧縮を実行し、デザイナーの視点に戻ります。
もうお気づきかと思いますが、高品質の UI を作成して維持するのは大変な作業です。
さまざまな状態は状態管理ライブラリでカバーされていますが、スタイルはどのように管理されていたのでしょうか?
解決策がまだ確立されていないため、方法論、ライブラリ、フレームワークが次々と登場していますが、3 つの主要なパラダイムがあります。
その中でも、JS の CSS は、スタイルの表現と管理に根本的に異なるアプローチを使用するパラダイムのように感じられます。
これは、JS の CSS が メカニズム に似ているのに対し、セマンティック CSS とアトミック CSS は ポリシー に似ているためです。
この違いのため、JS の CSS は他の 2 つのアプローチとは分けて説明する必要があります。 (画像出典)
JS メカニズムでの CSS について議論するとき、CSS のプリ/ポスト プロセッサーが思い浮かぶかもしれません。
同様に、ポリシーについて話すとき、「CSS 方法論」が思い浮かぶかもしれません。
そこで、次の順序でスタイル管理方法を紹介します: JS の CSS、プロセッサ、セマンティック CSS とアトミック CSS、その他のスタイル方法論。
では、JSにおけるCSSの正体は何でしょうか?
答えは上記の定義にあります。
JavaScriptで記述し、コンポーネント単位でCSSを分離します。
これらの中で、CSS 分離は既存の CSS に十分に適用でき、グローバル名前空間と分離破壊の問題を解決できます。
これは CSS モジュールです。
上記のCSS in JS解析記事へのリンクをもとに、機能を以下のように分類してみました。
各機能にはトレードオフがあり、これらは JS で CSS を作成する際の重要な要素です。
SSR(サーバーサイドレンダリング) と RSC(React Server Component) です。
これらはフロントエンドを代表するReactやNEXTが目指す方向性であり、実装に大きな影響を与える重要なものです。
文字列として抽出する必要があり、ストリーミングへの応答が必要です。スタイル付きコンポーネントの例と同様に、追加の設定が必要な場合があります。 (画像出典)
Server components have more limitations. [1, 2]
Server and client components are separated, and dynamic styling based on props, state, and context is not possible in server components.
It should be able to extract .css files as mentioned below.
As these are widely known issues, I will not make any further mention of them.
The notable point in the CSS output is Atomic CSS.
Styles are split and output according to each CSS property.