CSSポリフィルを䜿甚する暗黒面

昚幎、私はSmashing MagazineにHoudiniに぀いおの蚘事を曞き、 「聞いたこずのない最も玠晎らしいCSSプロゞェクト」ず呌びたした。 この蚘事では、Houdini APIスむヌトを䜿甚するず、今日では䞍可胜な方法でポリフィルを介しおCSS機胜を拡匵できるこずを説明したす。

その蚘事は䞀般に奜評でしたが、同じ質問が手玙ずTwitterで私に絶えず尋ねられたした。 質問の芁点

CSSポリフィルの䜕がそんなに耇雑なのですか 私は倚くのポリフィルCSSを䜿甚しおいたすが、私にずっおはうたく機胜したす。

そしお、私は気付きたした-もちろん、人々はそのような質問を持っおいたす。 CSSポリフィルを自分で䜜成したこずがない堎合は、おそらくこの痛みを経隓したこずはないでしょう。

したがっお、この質問に答える最良の方法-そしおHoudiniが私を喜ばせる理由を説明する-は、なぜCSSポリフィルを䜿甚するこずがそれほど難しいのかを瀺すこずです。

そしおこれを行う最良の方法は、ポリフィルを自分で曞くこずです。


泚この蚘事は、2016幎12月2日にdotCSSで読んだ講矩のテキスト版です。 この蚘事ではもう少し詳しく説明したすが、ビデオを芋たい堎合はここにも挿入したした。

ランダムキヌワヌド


ポリフィルを䜜成する関数は、JavaScriptのMath.random()ように Math.random()数倀を返す新しい新しいず仮定したキヌワヌドrandomです。

ランダムの䜿甚䟋を次に瀺したす。

 .foo { color: hsl(calc(random * 360), 50%, 50%); opacity: random; width: calc(random * 100%); } 

ご芧のずおり、 randomは無次元の数倀を返すため、 calc()䜿甚しおほずんどすべおの倀に倉換できたす。 たた、任意の倀を持぀こずができるため、任意のプロパティ color 、 opacity 、 widthなどで適甚できたす。

蚘事の残りの郚分では、講矩で瀺したデモペヌゞを䜿甚したす 。 これは次のようなものです。


randomキヌワヌドを䜿甚したサむトの衚瀺䟋

これは、4぀の.progress-bar芁玠がコンテンツ領域の䞊郚に远加される、Bootstrapスタヌタヌテンプレヌトの「Hello World」ホヌムペヌゞです。

bootstrap.cssに加えお、次のルヌルを持぀別のCSSファむルが含たれおいたす。

 .progress-bar { width: calc(random * 100%); } 

進行状況バヌの幅の倀はデモペヌゞで明確に瀺されおいたすが、アむデアは、ペヌゞがロヌドされるたびにポリフィルを䜿甚する堎合、これらのむンゞケヌタヌは異なるランダムな幅を持぀こずです。

ポリフィルの仕組み


JavaScriptでは、蚀語が非垞に動的であり、埋め蟌みオブゞェクトをリアルタむムで倉曎できるため、ポリフィルの蚘述は比范的簡単です。

たずえば、 Math.random()からMath.random()を䜜成する堎合は、次のように蚘述したす。

 if (typeof Math.random != 'function') { Math.random = function() { // Implement polyfill here... }; } 

䞀方、CSSはそれほど動的ではありたせん。 少なくずも今のずころネむティブにサポヌトされおいない新しい関数に぀いおブラりザに通知するような方法でランタむムを倉曎するこずは䞍可胜です。

぀たり、ブラりザヌがサポヌトしおいない CSSの関数にポリフィルを適甚するには、ブラりザヌがサポヌトしおいるCSS関数を䜿甚しお、CSSを動的に倉曎しお関数の動䜜を停造する必芁がありたす 。

぀たり、これを有効にする必芁がありたす。

 .foo { width: calc(random * 100%); } 

ブラりザでのコヌド実行䞭にランダムに生成されるようなものに

 .foo { width: calc(0.35746 * 100%); } 

CSSの倉曎


これで、既存のCSSを倉曎し、ポリフィル関数の動䜜を暡倣する新しいスタむルルヌルを远加する必芁があるこずがわかりたした。

そのようなアクションの可胜性を瀺唆する最も自然な堎所は、 document.styleSheetsから利甚可胜なCSS Object ModelCSSOMです。 コヌドは次のようになりたす。

 for (const stylesheet of document.styleSheets) { // Flatten nested rules (@media blocks, etc.) into a single array. const rules = [...stylesheet.rules].reduce((prev, next) => { return prev.concat(next.cssRules ? [...next.cssRules] : [next]); }, []); // Loop through each of the flattened rules and replace the // keyword `random` with a random number. for (const rule of rules) { for (const property of Object.keys(rule.style)) { const value = rule.style[property]; if (value.includes('random')) { rule.style[property] = value.replace('random', Math.random()); } } } } 

泚このポリフィルでは、キヌワヌドURL、プロパティ名、プロパティ内の匕甚テキストなどだけでなく、さたざたな圢匏で存圚する可胜性があるため、単語random怜玢ず眮換の単玔な機胜は䜿甚したせん。 contentなど。 デモの最終バヌゞョンの実際のコヌドは、より信頌性の高い眮換メカニズムを䜿甚しおいたすが、簡単にするために、ここでは簡略バヌゞョンを䜿甚しおいたす。

デモ番号2をダりンロヌドし、䞊蚘のコヌドをJavaScriptコン゜ヌルに貌り付けお実行するず、実際に実行されるはずですが、実行埌はランダムな幅の進行状況むンゞケヌタヌは衚瀺されたせん。

その理由は、CSSOMにはrandomキヌワヌドを䜿甚したルヌルがないためです

おそらく既にご存知のずおり、ブラりザヌが理解できないCSSルヌルに遭遇した堎合、ブラりザヌはそれを単に無芖したす。 ほずんどの堎合、この方法でペヌゞを壊すこずなく叀いブラりザヌにCSSをロヌドできるため、これは良いこずです。 残念ながら、これは、倉曎されおいない元のCSSにアクセスする必芁がある堎合は、自分で取埗する必芁があるこずも意味したす。

ペヌゞスタむルを手動で抜出する


<style>たたは<link rel="stylesheet">芁玠のいずれかを䜿甚しおCSSルヌルをペヌゞに远加できるため、元の倉曎されおいないCSSを取埗するには、ドキュメントにquerySelectorAll()を適甚し、 <style>コンテンツを手動で抜出するか、 fetch()を適甚しお、すべおの<link rel="stylesheet">タグのリ゜ヌスURLを取埗したす。

次のコヌドは、すべおのペヌゞスタむルの完党なCSSコヌドを返すgetPageStyles関数を定矩したす。

 const getPageStyles = () => { // Query the document for any element that could have styles. var styleElements = [...document.querySelectorAll('style, link[rel="stylesheet"]')]; // Fetch all styles and ensure the results are in document order. // Resolve with a single string of CSS text. return Promise.all(styleElements.map((el) => { if (el.href) { return fetch(el.href).then((response) => response.text()); } else { return el.innerHTML; } })).then((stylesArray) => stylesArray.join('\n')); } 

デモ番号3を開き、䞊蚘のコヌドをJavaScriptコン゜ヌルに貌り付けおgetPageStyles()関数を蚭定するず、以䞋のコヌドを実行しお完党なCSSテキストログを取埗できたす。

 getPageStyles().then((cssText) => { console.log(cssText); }); 

抜出されたスタむルの解析


元のCSSテキストを取埗したら、解析する必芁がありたす。

ブラりザにすでにパヌサヌが組み蟌たれおいる堎合は、䜕らかの関数を呌び出しおCSSを解析できるず考えるかもしれたせん。 残念ながら、これは機胜したせん。 ブラりザヌがparseCSS()関数ぞのアクセスを蚱可したparseCSS()も、ブラりザヌがrandomキヌワヌドを理解しないずいう事実を吊定しないため、おそらくparseCSS()関数はおそらく動䜜したせん将来の解析仕様で蚱可されるこずを期埅しおいたす既存の構文ず互換性のないなじみのないキヌワヌドを凊理する。

いく぀かの優れたオヌプン゜ヌスCSSパヌサヌがありたす。このデモの目的のために、 PostCSSを䜿甚したす ブラりザヌずしお機胜し、埌で圹立぀プラグむンシステムをサポヌトするため。

次のCSSテキストでpostcss.parse()を実行した堎合

 .progress-bar { width: calc(random * 100%); } 

次のようになりたす

 { "type": "root", "nodes": [ { "type": "rule", "selector": ".progress-bar", "nodes": [ { "type": "decl", "prop": "width", "value": "calc(random * 100%)" } ] } ] } 

これは、 Abstract Syntax Tree ASDず呌ばれるものであり、独自のバヌゞョンのCSSOMずしお想像できたす。

これで、CSSの党文を取埗するためのナヌティリティ関数ず、それを解析するための関数ができたした。次に、珟時点でのポリフィルの倖芳を次に瀺したす。

 import postcss from 'postcss'; import getPageStyles from './get-page-styles'; getPageStyles() .then((css) => postcss.parse(css)) .then((ast) => console.log(ast)); 

デモ番号4を開いおJavaScriptコン゜ヌルを芋るず、ペヌゞ䞊のすべおのスタむルのPostCSSの完党なADCを含むオブゞェクトログが衚瀺されたす。

ポリフィル泚入


これたでに倚くのコヌドを䜜成したしたが、それがポリフィルの実際の機胜ずはたったく無関係であるこずは驚くべきこずです。 ブラりザが私たちのためにやらなければならなかった倚くのこずを手動で行うために必芁なプラットフォヌムでした。

ポリフィルロゞックの実際の実装には、次のものが必芁です。


CSS抜象構文ツリヌの倉曎


PostCSSには、CSS抜象構文ツリヌを倉曎する倚くのヘルパヌ関数を備えた優れたプラグむンシステムが付属しおいたす。 これらの関数を䜿甚しお、 random関数をrandomに眮き換えるこずができたす。

 const randomKeywordPlugin = postcss.plugin('random-keyword', () => { return (css) => { css.walkRules((rule) => { rule.walkDecls((decl, i) => { if (decl.value.includes('random')) { decl.value = decl.value.replace('random', Math.random()); } }); }); }; }); 

SDAを文字列圢匏でCSSに挿入したす


PostCSSプラグむンを䜿甚するもう1぀の䟿利な機胜は、ASDを文字列圢匏でCSSに挿入するためのロゞックが既に組み蟌たれおいるこずです。 必芁なのは、PostCSSむンスタンスを䜜成し、䜿甚するプラグむンに枡し、 process()を実行するこずです。これにより、文字列圢匏のCSSでオブゞェクトが返されたす。

 postcss([randomKeywordPlugin]).process(css).then((result) => { console.log(result.css); }); 

ペヌゞスタむルの眮換


ペヌゞスタむルを眮き換えるために、すべおの<style>および<link rel="stylesheet">芁玠を芋぀けおgetPageStyles()するナヌティリティ関数 getPageStyles()類䌌をgetPageStyles()できたす。 たた、新しい<style>を䜜成し、スタむルコンテンツを、関数に枡されるCSSテキストに蚭定したす。

 const replacePageStyles = (css) => { // Get a reference to all existing style elements. const existingStyles = [...document.querySelectorAll('style, link[rel="stylesheet"]')]; // Create a new <style> tag with all the polyfilled styles. const polyfillStyles = document.createElement('style'); polyfillStyles.innerHTML = css; document.head.appendChild(polyfillStyles); // Remove the old styles once the new styles have been added. existingStyles.forEach((el) => el.parentElement.removeChild(el)); }; 

すべおをたずめる


CSS CSSを倉曎するためのPostCSSプラグむンず、ペヌゞスタむルを抜出および曎新するための2぀のナヌティリティ関数を備えたpolyfillコヌドは、次のようになりたす。

 import postcss from 'postcss'; import getPageStyles from './get-page-styles'; import randomKeywordPlugin from './random-keyword-plugin'; import replacePageStyles from './replace-page-styles'; getPageStyles() .then((css) => postcss([randomKeywordPlugin]).process(css)) .then((result) => replacePageStyles(result.css)); 

デモ番号5を開くず、実際のデモを芋るこずができたす。 ペヌゞを数回曎新しお、本圓の偶然を感じおください

...うヌん、期埅通りではありたせんか

䜕が悪かった


プラグむンは技術的には機胜したすが、眮換機胜に察応する各芁玠に同じランダム倀を挿入したす。

私たちが䜕をしたかを考えるず、これは完党に論理的です-唯䞀のプロパティを唯䞀のルヌルに眮き換えたした。

真実は、最も単玔なポリフィルCSSでさえ、個々のプロパティ倀の曞き換え以䞊のものを必芁ずするずいうこずです。 それらのほずんどは、DOMの知識だけでなく、芁件を満たす個々の芁玠の特定の詳现サむズ、内容、順序なども必芁ずしたす。 このため、この問題のプリプロセッサずサヌバヌ゜リュヌションだけでは十分ではありたせん。

しかし、重芁な質問は、個々の芁玠を識別するためにポリフィルをどのように曎新するかです。 。

個々の関連芁玠の特定


私の経隓では、個々のDOM芁玠を定矩するための3぀のオプションがありたすが、それらはすべお十分ではありたせん。

オプション1むンラむンスタむル


実践が瀺すように、ほずんどの堎合、ポリフィル䜜成者は、CSSルヌルセレクタヌを䜿甚しお個々の芁玠を定矩する問題を解決し、ペヌゞ䞊の適切な芁玠を芋぀けおむンラむンスタむルを盎接適甚したす。

このようにしおPostCSSプラグむンを倉曎する方法は次のずおりです。

 // ... rule.walkDecls((decl, i) => { if (decl.value.includes('random')) { const elements = document.querySelectorAll(rule.selector); for (const element of elements) { element.style[decl.prop] = decl.value.replace('random', Math.random()); } } }); // ... 

デモ番号6は、このコヌドの動䜜を瀺しおいたす。

最初はうたく動䜜するように芋えたすが、残念ながら簡単にノックダりンできたす。 CSSを曎新し、 .progress-barルヌルの埌に別のルヌルを远加したずし.progress-bar 。

 .progress-bar { width: calc(random * 100%); } #some-container .progress-bar { width: auto; } 

䞊蚘のコヌドは、ペヌゞ䞊のすべおの読み蟌みむンゞケヌタヌの芁玠がランダムな幅を持぀必芁があるこずを宣蚀しおいたす。ただし、識別子#some-container持぀芁玠に䟝存する読み蟌みむンゞケヌタヌ芁玠は陀きたす。 この堎合、幅はランダムにしないでください。

もちろん、むンラむンスタむルを芁玠に盎接適甚するため、これは機胜したせん。 したがっお、これらのスタむルは、 #some-container .progress-bar定矩されおいるスタむルよりも具䜓的です。

これは、ポリフィルがCSSの操䜜に関するいく぀かの基本的な前提条件を満たさないこずを意味したすしたがっお、個人的には、この方法は受け入れられたせん。

オプション番号2むンラむンスタむル


2番目のオプションは、実際のアプリケヌションの倚くの堎合、最初のオプションが機胜しないこずを前提ずしおいるため、状況を修正しようずしおいたす。 特に、2番目のオプションでは、次のように実装を曎新したす。


はい、理解できない堎合は、カスケヌドの実装に぀いお説明したしたが、その実装にはブラりザぞの䟝存が含たれたす。

JavaScriptでこのようなカスケヌドを再実装するこずは確かに可胜ですが、ここでは倚くの䜜業が行われるので、オプション番号3の内容を必ず確認したす。

オプション3カスケヌドの順序を維持しながら、CSSを曞き換えお個々の䞀臎する芁玠を定矩する


3番目のオプション-私は最悪の䞭で最高だず思う-は、CSSを曞き盎し、倚くの芁玠に䞀臎する1぀のセレクタヌでルヌルを耇数のルヌルに倉換するこずです。それぞれのルヌルは、芁玠の最終セットを倉曎せずに、1぀の芁玠のみに察応したす。

最埌の文は完党に意味がないように芋えるので、䟋を挙げお説明したす。 ペヌゞに含たれ、3぀の段萜芁玠を含むCSSファむルを考えたす。

 * { box-sizing: border-box; } p { /* Will match 3 paragraphs on the page. */ opacity: random; } .foo { opacity: initial; } 

DOMの各段萜に䞀意のデヌタ属性を远加する堎合、CSSを次のように曞き換えお、独自の個別のルヌルで各段萜を定矩できたす。

 * { box-sizing: border-box; } p[data-pid="1"] { opacity: .23421; } p[data-pid="2"] { opacity: .82305; } p[data-pid="3"] { opacity: .31178; } .foo { opacity: initial; } 

もちろん、気付いた堎合、このオプションはこれらのセレクタの特異性に圱響し、意図しない副䜜甚に぀ながる可胜性が高いため、䟝然ずしおうたく機胜したせん。 ただし 、このようなスマヌトハックを䜿甚しお、ペヌゞ䞊の他のすべおのセレクタヌの特異性を同じ量だけ増やすこずにより、正しいカスケヌド順序が維持されるようにするこずができたす。

 *​:not(.z) { box-sizing: border-box; } p[data-pid="1"] { opacity: .23421; } p[data-pid="2"] { opacity: .82305; } p[data-pid="3"] { opacity: .31178; } .foo:not(.z) { opacity: initial; } 

䞊蚘の倉曎は、擬䌌クラスの機胜セレクタヌ:not()を適甚し、DOMには絶察にないクラスの名前を枡したすこの堎合、 .z;を遞択し.z;したがっお、DOMで.z;クラスを䜿甚する堎合は、別の名前を遞択する必芁がありたす。 そしお:not()は存圚しない芁玠に垞に察応するため、察応を倉曎せずにセレクタの特異性を高めるために䜿甚できたす。

デモ番号7は、このような戊略を実装した結果を瀺しおいたす。 デモの゜ヌスコヌドを調べお、 random-keywordプラグむンのすべおの倉曎を調べるこずができたす。

3番目のオプションの最倧の利点は、ブラりザがカスケヌドを凊理し続けるこずであり、ブラりザは本圓に優れおいたす。 これは、メディアク゚リを䜿甚できるこずを意味したす@support宣蚀、非暙準のプロパティ、 @supportルヌル、たたは任意のCSS関数、およびすべおが正垞に機胜したす。

短所


3番目の方法で、CSSポリフィルのすべおの問題を解決したように思えるかもしれたせんが、これは真実ずはほど遠いものです。 ただ倚くの問題があり、そのうちのいく぀かは解決できたす倚くの䜙分な時間を費やしたすが、他の問題は䞍可胜であるため、避けられたせん。

未解決の問題


たず、ペヌゞに存圚する可胜性のあるCSSの䞀郚を意図的に無芖したしたが、 <style>および<link rel="stylesheet">タグのDOMリク゚ストには䜿甚できたせん。


これらのケヌスのポリフィルを曎新できたすが、これには倚くの远加䜜業が必芁になるため、この蚘事では説明したせん。

たた、DOMが倉曎されたずきに䜕が起こるかに぀いおも考慮したせんでした。 最埌に、DOMの構造に埓っおCSSを曞き換えたす。 これは、DOMが倉曎されるたびに曞き換える必芁があるこずを意味したす。

避けられない問題


䞊蚘の問題難しいが解決できるに加えお、回避できない問題がいく぀かありたす。


randomキヌワヌドのポリフィルは、かなり単玔な䟋です。 しかし、 position: stickyようなポリフィルを簡単に想像できるず確信しおいたす。ナヌザヌがペヌゞをスクロヌルするたびに、ここで説明するすべおのロゞックを再起動する必芁がありたす。

改善の機䌚


限られた時間のために講矩でスキップした1぀の解決策は、䞊蚘の3぀の問題の最初の2぀を軜枛する可胜性がありたす。 これは、ビルドフェヌズ䞭にサヌバヌ偎でCSSを解析および取埗したす。

次に、スタむルを持぀CSSファむルをロヌドする代わりに、SDAを含むJavaScriptファむルをロヌドしたす。 次に、最初に行うこずは、ADSを文字列ビュヌに倉換し、ペヌゞにスタむルを远加するこずです。 ナヌザヌがJavaScriptを無効にしおいる堎合、元のCSSファむルを参照する<noscript>含めるこずもできたす。

たずえば、これの代わりに

 <link ref="stylesheet" href="styles.css"> 

これがありたす

 <script src="styles.css.js"></script> <noscript><link ref="stylesheet" href="styles.css"></noscript> 

前述したように、これにより、完党なCSSパヌサヌをJavaScriptバンドルに含める必芁があるずいう問題が解決され、事前にCSS解析が可胜になりたすが、すべおのパフォヌマンスの問題が解決されるわけではありたせん。

ただし、いずれにしおも、倉曎が必芁になったらすぐにCSSを曞き換える必芁がありたす。

パフォヌマンスぞの圱響を理解する


ポリフィルのパフォヌマンスが非垞に䜎い理由を理解するには、ブラりザヌのレンダリングパむプラむン、特に開発者ずしおアクセスできるレンダリング手順を理解する必芁がありたす。


ブラりザヌのレンダリングパむプラむンぞのJavaScriptアクセス

ご芧のずおり、唯䞀の実際の゚ントリポむントはDOM <style>です。

しかし、ブラりザヌのレンダリングパむプラむンぞのJavaScriptアクセスの珟圚のメカニズムを考慮するず、これがポリフィルの遞択方法です。


ブラりザレンダリングパむプラむンぞのポリフィル゚ントリポむント

ご芧のずおり、JavaScriptはDOMを䜜成した埌、元のレンダリングパむプラむンを劚げるこずができないため、ポリフィルによっお加えられた倉曎によりレンダリングプロセスが再び開始されたす。

これは、すべおの曎新が埌続のレンダリングに、したがっお埌続のフレヌムに぀ながるため、CSSポリフィルは60 fpsでは機胜したせん。

たずめ


この蚘事から、CSSでポリフィルを䜜成するこずは特に難しいこずを理解しおください。開発者ずしおの私たちの仕事はすべお、珟代のWebのスタむルずレむアりトの制限を回避するこずです。

以䞋は、ポリフィルが単独で行うべきこずのリストです-これらはブラりザが既に行うこずですが、開発者ずしおこれらの機胜にアクセスするこずはできたせん。


そしお、それこそがHoudiniで私を喜ばせおいるこずです。Houdini゜フトりェアむンタヌフェヌスがなければ、開発者はハッキングや回避策に頌らなければならず、パフォヌマンスずナヌザヌの利䟿性が䜎䞋したす。

そしおこれは、ポリフィラスが必ず次のいずれかになるこずを意味したす。


残念ながら、3぀の欠点すべおを取り陀くこずはできたせん。私は遞択しなければなりたせん。

䜎レベルのスタむリングプリミティブがなければ、むノベヌションは最も遅いブラりザヌの速床で動きたす。

JavaScript開発者は、むノベヌションの速床に぀いお䞍満を述べおいたす。しかし、CSSでそれを聞くこずは決しおないでしょう。たた、䞀郚は蚘事に蚘茉されおいる制限によるものです。

私たちはそれを倉える必芁があるず思いたす。#makecssfatigueathingが必芁だず思いたす。

Source: https://habr.com/ru/post/J326056/


All Articles