この記事は https://css-tricks.com/getting-javascript-to-talk-to-css-and-sass/ の翻訳記事となります。
(2020/4/3 寄稿記事)
JavaScriptとCSSは20年以上にわたって依存し合っているわけですが、いまだ相互のデータのやりとりには大変な苦労があります。
もちろん、これまでも数多くのアプローチが紹介されていますが、ここではシンプルかつ直感的なアプローチを紹介したいと思います。
ここで言うシンプルかつ直感的と言うのは、構造を変えての試みということではなく、CSSカスタムプロパティやSass変数の利用について述べることにします。
CSSカスタムプロパティとJavaScript
カスタムプロパティというものはそうたいそうなものではありません。ブラウザーのサポートが開始されてから、JavaScriptからその値を操作することが可能になりました。
カスタムプロパティをJavaScriptで利用する方法にはいくつかありますが、 setProperty
を利用する方法が挙げられます。
document.documentElement.style.setProperty("--padding", 124 + "px"); // 124px
CSS変数を抽出するには、 getComputedStyle
を使います。このロジックの背景はいたってシンプルです。カスタムプロパティはスタイルの一部ですので、算出スタイル (computed style) の一部でもあると言えるのです。
getComputedStyle(document.documentElement).getPropertyValue('--padding') // 124px
同様に getPropertyValue
がありますが、これによりHTML上のインラインスタイルの値を抽出することができます。
document.documentElement.style.getPropertyValue("--padding'"); // 124px
カスタムプロパティはスコープ定義となるので、特定の要素から算出スタイル (computed style) を取得する必要があります。
ここでは、前述のsetProperty
を使って :root に変数を定義しているので値を得ることができています。
Sass変数をJavaScriptで利用する
Sassは事前処理(pre-processing)言語ですので、ウェブサイトの一部として機能するには事前にCSSへの変換が行われます。
そのため、CSSカスタムプロパティと同じようにJavaScriptからアクセスすることはできません。CSSカスタムプロパティは算出スタイル (computed style) としてDOM上でアクセスが可能だからです。
これを変えるためにbuildプロセスを変更する必要があります。とはいえ、多くの場合loaderがbuildプロセスをになってくれるのでそう大きな変更は必要ないと思います。しかし、そうではないプロジェクトの場合は、Sassモジュールのインポート (import) と翻訳 (tranlating) を行う3つのモジュールを利用して以下のように webpack の設定を行います。
module.exports = {
// ...
module: {
rules: [
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"]
},
// ...
]
}
};
To make Sass (or, specifically, SCSS in this case) variables available to JavaScript, we need to “export” them.
Sass変数(今回は SCSS)をJavaScriptで利用するには :export
する必要があります。
// variables.scss
$primary-color: #fe4e5e;
$background-color: #fefefe;
$padding: 124px;
:export {
primaryColor: $primary-color;
backgroundColor: $background-color;
padding: $padding;
}
この :export
はwebpackが変数をimportするための魔法です。何が良いかというと、変数名を変えることができる (camelCase) ことと、利用する変数を選別できることです。
次にこのSassファイル (variables.scss) をJavaScriptでimportします。これで、変数へのアクセスが可能となります。
import variables from './variables.scss';
/*
{
primaryColor: "#fe4e5e"
backgroundColor: "#fefefe"
padding: "124px"
}
*/
document.getElementById("app").style.padding = variables.padding;
ただ、この :export
にはいくつかの制約があるので述べておいた方が良いと思います。
It must be at the top level but can be anywhere in the file.
- Topレベル階層に位置する必要があるが、ファイル内のどこで定義しても良い
- 同一ファイル内、複数箇所で定義されている場合、キー (key) と値 (value) は結合され一緒にexportされる
- キーの重複がある場合、後続のキーの値が優先される
- 値にはCSSで有効な任意の文字が利用できる(スペースも可)
- 値には引用符(
'
"
)は不要(リテラル文字列として扱われるため)
There are lots of ways having access to Sass variables in JavaScript can come in handy. I tend to reach for this approach for sharing breakpoints. Here is my breakpoints.scs file, which I later import in JavaScript so I can use the matchMedia() method to have consistent breakpoints.
JavaScriptからSass変数にアクセスすることが有用であるケースは多くありますが、筆者の場合、breakpoint を共有するために利用することがよくあります。
以下の breakpoints.scss
をJavaScriptでimportし、 matchMedia()
で一貫した breakpoint の利用を可能としています。
// Sass variables that define breakpoint values
$breakpoints: (
mobile: 375px,
tablet: 768px,
// etc.
);
// Sass variables for writing out media queries
$media: (
mobile: '(max-width: #{map-get($breakpoints, mobile)})',
tablet: '(max-width: #{map-get($breakpoints, tablet)})',
// etc.
);
// The export module that makes Sass variables accessible in JavaScript
:export {
breakpointMobile: unquote(map-get($media, mobile));
breakpointTablet: unquote(map-get($media, tablet));
// etc.
}
もう一つのアプローチとしてアニメーションがあります。 アニメーションで利用する duration
は通常CSSSで保持されますが、複雑なアニメーションになるとJavaScriptが必要となります。
// animation.scss
$global-animation-duration: 300ms;
$global-animation-easing: ease-in-out;
:export {
animationDuration: strip-unit($global-animation-duration);
animationEasing: $global-animation-easing;
}
変数 export に
strip-unit()
を利用していますが、これはJavaScript側でのparse処理を容易にするためです。
CSS、SassとJavaScriptでのデータのやりとりが簡単にできることは嬉しいことで、このような変数の共有はコードをシンプルかつ無駄のないもの(原文にはDRYとあります)にしてくれます。
もちろん、他にもこれを実現するアプローチはあります。Les James氏は2017に興味深いアプローチを紹介しています。彼が紹介しているのは、JSONを利用したデータの共有です。ただ、偏った見方になるかもしれませんが、この記事で紹介したアプローチが最もシンプルで直感的であると思います。すでに運用しているCSSやJavaScriptへの面倒な変更は不要です。
他にもアプローチがあれば、ぜひコメントください。どう解決しているのかを知りたいです!