tag:crieit.net,2005:https://crieit.net/tags/Bootstrap4/feed 「Bootstrap4」の記事 - Crieit Crieitでタグ「Bootstrap4」に投稿された最近の記事 2020-12-21T23:39:29+09:00 https://crieit.net/tags/Bootstrap4/feed tag:crieit.net,2005:PublicArticle/16402 2020-12-21T23:39:29+09:00 2020-12-21T23:39:29+09:00 https://crieit.net/posts/honoka-bootstrap-use-in-node-modules-20201221 Honoka を node_modules 内で使用する <p>今までは <a target="_blank" rel="nofollow noopener" href="https://honokak.osaka">Honoka</a> と <a target="_blank" rel="nofollow noopener" href="https://getbootstrap.com">Bootstrap</a> 本体を別のディレクトリに引っ張り出して合体させて使用していたのですが、 <code>node_modules</code> の中にそのまま残っている状態で使用したくなったのでメモしておきます。</p> <h2 id="経緯"><a href="#%E7%B5%8C%E7%B7%AF">経緯</a></h2> <p>今までは以下のように Honoka と Bootstrap を <code>node_modules</code> 外のディレクトリに引っ張り出して合体させていました。</p> <ol> <li><code>src/scss/assets/bootstrap/</code> ディレクトリを作成</li> <li><code>node_modules/bootstrap-honoka/scss/bootstrap.scss</code> を 1.のディレクトリにコピー</li> <li><code>src/scss/assets/bootstrap/honoka/</code> ディレクトリを作成</li> <li><code>node_modules/bootstrap-honoka/scss/honoka/_honoka.scss</code> を 3.のディレクトリにコピー</li> <li><code>src/scss/assets/bootstrap/honoka/honoka/</code> ディレクトリを作成</li> <li><code>node_modules/bootstrap-honoka/scss/honoka/</code> ディレクトリの <code>.scss</code> ファイルで4. <code>_honoka.scss</code> 以外を 5.のディレクトリにコピー</li> <li><code>src/scss/assets/bootstrap/honoka/bootstrap/scss</code> ディレクトリを作成</li> <li>7.のディレクトリの下に <code>node_modules/bootstrap/scss</code> 以下のファイル・ディレクトリをコピー</li> </ol> <p>これを npm scripts 等でひとまとめのタスクにして実行していました。</p> <p>そもそも、何故今までこの方法を取ったかというと、以下の2つの理由に拠ります。</p> <ol> <li><code>node_modules</code> 下に Honoka と Bootstrap があるそのままの状態では上手く動かなかったため</li> <li><code>src/scss/assets/bootstrap/honoka/_honoka.scss</code> を Git 管理対象にしたかったため <ul> <li>該当ファイルはコンポーネントの読み込みを司っており、不要なコンポーネントを切った状態を Git 管理したかった</li> </ul></li> </ol> <p>しかし、 <a target="_blank" rel="nofollow noopener" href="https://labor.ewigleere.net/2020/11/09/scss_transfer_libsass_dartsass/">LibSass から Dart Sass への乗り換え</a>たり、 <a target="_blank" rel="nofollow noopener" href="https://labor.ewigleere.net/2020/12/02/bootstrap_paint_in_variable/">Dart Sass (@use, @forward 使用)で Bootstrap 4 の変数やマップを上書きする</a>で Scss 周りを改修している中で、もう少し良いやり方がないか、と思い始めた次第です。最初の1回だけとはいえ、ファイルコピーの待ち時間も必要になりますし……。</p> <h2 id="結果"><a href="#%E7%B5%90%E6%9E%9C">結果</a></h2> <p>結果、「<code>src/scss/assets/bootstrap/honoka/_honoka.scss</code> の代わりとなる各コンポーネント読み込みの Scss を自前で用意する」形で <code>node_modules</code> に Honoka と Bootstrap を残したまま使用できるようにしました。</p> <h3 id="package.json"><a href="#package.json">package.json</a></h3> <pre><code class="json"> "dependencies": { // 略 "bootstrap": "^4.5.3", "bootstrap-honoka": "^4.4.0", // 略 }, </code></pre> <p><code>package.json</code> はそのまま。</p> <h3>src/scss/global/<em>framework</em>.scss</h3> <pre><code class="scss">@charset "utf-8"; //@forward "../assets/bootstrap/bootstrap"; //bootstrap @forward "honoka/honoka_import"; //bootstrap </code></pre> <p>今まで <code>src/scss/assets/</code> 下のファイルを読み込ませていたのを、自前ファイルに切り替え。</p> <h3 id="src/scss/global/honoka/_honoka_import.scss"><a href="#src%2Fscss%2Fglobal%2Fhonoka%2F_honoka_import.scss">src/scss/global/honoka/_honoka_import.scss</a></h3> <pre><code class="scss">@charset "utf-8"; // Core functions @import "node_modules/bootstrap/scss/functions"; // Honoka variables @import "node_modules/bootstrap-honoka/scss/honoka/variables"; @import "node_modules/bootstrap/scss/variables"; @import "node_modules/bootstrap/scss/mixins"; @import "node_modules/bootstrap/scss/root"; // Core CSS @import "node_modules/bootstrap/scss/reboot"; @import "node_modules/bootstrap/scss/type"; @import "node_modules/bootstrap/scss/images"; @import "node_modules/bootstrap/scss/code"; @import "node_modules/bootstrap/scss/grid"; @import "node_modules/bootstrap/scss/tables"; @import "node_modules/bootstrap/scss/forms"; @import "node_modules/bootstrap/scss/buttons"; // Components @import "node_modules/bootstrap/scss/transitions"; @import "node_modules/bootstrap/scss/dropdown"; @import "node_modules/bootstrap/scss/button-group"; @import "node_modules/bootstrap/scss/input-group"; @import "node_modules/bootstrap/scss/custom-forms"; @import "node_modules/bootstrap/scss/nav"; @import "node_modules/bootstrap/scss/navbar"; @import "node_modules/bootstrap/scss/card"; @import "node_modules/bootstrap/scss/breadcrumb"; @import "node_modules/bootstrap/scss/pagination"; @import "node_modules/bootstrap/scss/badge"; @import "node_modules/bootstrap/scss/jumbotron"; @import "node_modules/bootstrap/scss/alert"; @import "node_modules/bootstrap/scss/progress"; @import "node_modules/bootstrap/scss/media"; @import "node_modules/bootstrap/scss/list-group"; @import "node_modules/bootstrap/scss/close"; @import "node_modules/bootstrap/scss/toasts"; @import "node_modules/bootstrap/scss/modal"; @import "node_modules/bootstrap/scss/tooltip"; @import "node_modules/bootstrap/scss/popover"; @import "node_modules/bootstrap/scss/carousel"; @import "node_modules/bootstrap/scss/spinners"; @import "node_modules/bootstrap/scss/utilities"; @import "node_modules/bootstrap/scss/print"; // Honoka original setting @import "node_modules/bootstrap-honoka/scss/honoka/override"; </code></pre> <p>今回用意した自前ファイル。<a target="_blank" rel="nofollow noopener" href="https://github.com/windyakin/Honoka/blob/v4.4.0/scss/honoka/_honoka.scss">Honoka/_honoka.scss at v4.4.0 · windyakin/Honoka</a>の内容をそのまま持ってきて、パスを調整したものになります。</p> <hr /> <p>これでファイルコピーやディレクトリを掘るタスクを整理できてすっきり。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.saaria.info/archives/4773">日本語サイト向けBootstrapのhonokaをwebpackでビルドする</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/windyakin/Honoka/blob/v4.4.0/scss/honoka/_honoka.scss">Honoka/_honoka.scss at v4.4.0 · windyakin/Honoka</a></li> </ul> arm-band tag:crieit.net,2005:PublicArticle/16364 2020-12-15T00:06:21+09:00 2020-12-15T00:06:21+09:00 https://crieit.net/posts/note-of-slick-in-modal-20201215 モーダルの中で Slick を動かす場合の諸注意(setPotion指定、 Uncaught TypeError: Cannot read property 'add' of null エラー回避のために slick-initialized クラスの確認、 unslick 指定) <p>モーダルのような、初期状態で display: none; がかかっているような要素の中で Slick を動かす場合にハマった事項のメモです。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://kenwheeler.github.io/slick/">slick - the last carousel you'll ever need</a></li> </ul> <h2 id="やりたかったこと"><a href="#%E3%82%84%E3%82%8A%E3%81%9F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">やりたかったこと</a></h2> <ul> <li>lazyLoad</li> <li>モーダルの中で Slick を動かす</li> <li>複数のモーダルがある(それぞれに Slick あり)</li> </ul> <p>シチュエーションとしてはこんなところ。</p> <h2 id="サンプル"><a href="#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB">サンプル</a></h2> <p>以下に一つずつ解説してきますが、動作確認のためのサンプルを置いておきます。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="http://scrunchy56.starfree.jp/test_slick/">Slick Test</a></li> </ul> <p>リポジトリも。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="http://scrunchy56.starfree.jp/test_slick/">Slick Test</a></li> </ul> <h2 id="1. lazyLoad"><a href="#1.+lazyLoad">1. lazyLoad</a></h2> <p>まずは <code>lazyLoad</code> ですが、これについては <a target="_blank" rel="nofollow noopener" href="https://kenwheeler.github.io/slick/">slick</a> で用意されている <code>lazyLoad</code> プロパティが使えます。</p> <pre><code class="javascript">$('slick').slick({ lazyLoad: 'ondemand' }); </code></pre> <p>プロパティはスライドする度に画像を読み込む <code>ondemand</code> とページ読み込み後に読み込む <code>progressive</code> の2つ。</p> <p>また、 <code>lazyLoad</code> で画像が読み込まれたときに発火するイベント <code>lazyLoaded</code> とエラー時に発火する <code>lazyLoadError</code> があるので、ケースによってはこれらも利用できそう。</p> <pre><code class="javascript">$('slick').on('lazyLoaded', function (event, slick, image, imageSource) { // lazy load succeed console.log(`${event.type}: ${imageSource} / ${image[0].alt} の読み込みが完了しました。`); // 「lazyLoaded: http://placehold.jp/667x375.jpg / Slick 1-4 の読み込みが完了しました。」のようなメッセージを出力 }); $('slick').on('lazyLoadError', function (event, slick, image, imageSource) { // lazy load failed console.log(`${event.type}: ${imageSource} の読み込みが失敗しました。`); }); $('slick').slick({ lazyLoad: 'ondemand' }); </code></pre> <p>ちなみに、 <code>lazyLoad</code> は JS 側だけではなく、 <strong>HTML 側にも既述が必要</strong>です。</p> <pre><code class="html"><ul class="c-slick" id="lazyLoad"> <li><img data-lazy="http://placehold.jp/667x375.jpg" src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/ajax-loader.gif" alt="Slick 1-1"></li> <li><img data-lazy="http://placehold.jp/667x375.jpg" src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/ajax-loader.gif" alt="Slick 1-2"></li> <li><img data-lazy="http://placehold.jp/667x375.jpg" src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/ajax-loader.gif" alt="Slick 1-3"></li> <li><img data-lazy="http://placehold.jp/667x375.jpg" src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/ajax-loader.gif" alt="Slick 1-4"></li> </ul> </code></pre> <p><code>img</code> タグの <code>src</code> 属性は画像が読み込まれるまでのダミー画像の指定、本番のスライドショーで表示させたい画像は<strong>独自の <code>data-lazy</code> 属性で指定</strong>、となります。</p> <p>ここまでがサンプルの「Slick 1」のケース。</p> <h2 id="2. モーダルの中で Slick を動かす"><a href="#2.+%E3%83%A2%E3%83%BC%E3%83%80%E3%83%AB%E3%81%AE%E4%B8%AD%E3%81%A7+Slick+%E3%82%92%E5%8B%95%E3%81%8B%E3%81%99">2. モーダルの中で Slick を動かす</a></h2> <h3 id="2.1. 普通に Slick"><a href="#2.1.+%E6%99%AE%E9%80%9A%E3%81%AB+Slick">2.1. 普通に Slick</a></h3> <p>さてここからが本題。</p> <p>例として Bootstrap 4 のモーダルコンポーネントを利用しますが、モーダルのような初期状態で <code>display: none;</code> となっている要素の中に Slick をセットすると動かないようです。</p> <p>原因としては、親要素が <code>display: none;</code> なので、 Slick の要素が <code>width: 0px;</code> で潰れてしまうようです(ついでに <code>height: 1px;</code> でもあった)。</p> <p>※サンプルの「Slick 2」の「モーダルを開く (setPotion 指定なし)」のケース</p> <h3 id="2.2. setPotion"><a href="#2.2.+setPotion">2.2. setPotion</a></h3> <p>ここで Slick 公式やいくつかの記事を見ると「 <code>setPotion</code> を指定すると良い」という記述が散見されます。</p> <p>これは <code>setPotion</code> を指定したタイミングで Slick 要素のサイズを再計算してくれるようです。</p> <p>今回は「モーダルが開いた時」なので、ボタンの <code>click</code> イベントにバインドすると良さそうです。これならば複数モーダル + Slick でもクリックされたモーダルのみ Slick が動くようになるので処理も軽減できます。</p> <p>注意点としては、 <code>click</code> イベント直後だとまだモーダルがフェードのエフェクトで表示されている途中なのでせっかく再計算しても潰れてしまう可能性があること。</p> <p>そのため、 <code>setTimeout</code> で処理する時間を指定しましょう。</p> <pre><code class="javascript">$('#setPotionModalButton').on('click', function () { // 0.3s 後に setPosition 付きで Slick 実行 const slickInit = setTimeout(() => { const $setPotion = $('#setPotion'); // Slick $setPotion.slick({ lazyLoad: 'ondemand' }); $setPotion.slick('setPosition'); }, 300); }); </code></pre> <p>※サンプルの「Slick 2」の「モーダルを開く (setPotion 指定あり)」のケース</p> <p>これでモーダルの中でも開くようになりました。よしよし……<strong>ではなかった</strong>。</p> <h2 id="3. Uncaught TypeError: Cannot read property 'add' of null エラーの回避 → .not('.slick-initialized')"><a href="#3.+Uncaught+TypeError%3A+Cannot+read+property+%27add%27+of+null+%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AE%E5%9B%9E%E9%81%BF+%E2%86%92+.not%28%27.slick-initialized%27%29">3. Uncaught TypeError: Cannot read property 'add' of null エラーの回避 → .not('.slick-initialized')</a></h2> <p>先程のモーダル、1回開くだけならば良いのですが、2回目以降モーダルを開こうとすると</p> <blockquote> <p>Uncaught TypeError: Cannot read property 'add' of null</p> </blockquote> <p>というエラーが発せられます。既に Slick が動いているところに、 <code>click</code> イベントでもう一度 Slick をバインドしようとしたので怒られてしまったわけです。</p> <p>回避策としては、 <code>.not('.slick-initialized')</code> を追加すること。</p> <p>Slick は初期化を完了すると該当要素に <code>slick-initialized</code> を付与するため、 <code>.not('.slick-initialized')</code> で「まだ Slick の初期化が済んでいない要素」と指定することでエラーを回避できる、ということですね。</p> <pre><code class="javascript">$('#unInitializedModalButton').on('click', function () { // 0.3s 後に setPosition 付きで Slick 実行 const slickInit = setTimeout(() => { const $unInitialized = $('#unInitialized'); // Slick $unInitialized.not('.slick-initialized').slick({ // .not('.slick-initialized') を追加 lazyLoad: 'ondemand' }); $unInitialized.slick('setPosition'); }, 300); }); </code></pre> <p>※サンプルの「Slick 3」のケース</p> <p><code>console.log()</code> 等で拾うと2回目以降 <code>$unInitialized.not('.slick-initialized')</code> は <code>undefined</code> になっていますが、エラーは回避できています。これで良し……<strong>ではなかった</strong>。</p> <h2 id="4. unslick"><a href="#4.+unslick">4. unslick</a></h2> <p>稀ですが、高速でモーダルを開いたり閉じたりして(あるいは何らかの理由で Slick の初期化が遅れて)しまうと、それ以降モーダルを開いても正常に Slick が表示されない現象が発生します。</p> <p>画像が多かったり重かったりすると出やすくなるかもしれません。</p> <p>ページ再読み込みすれば大丈夫と言えば大丈夫ですが……今回のシチュエーションではこの頻度がわりと大きく、無視できない状態でした。そのため、さらに対策を講じる必要がありました。</p> <p>そこで、以下のような処理を行うことにしました。</p> <ul> <li>モーダルを開くボタンクリック時 <ol> <li><code>click</code> イベント発火</li> <li><code>setTimeout</code> で 0.3s 後に Slick 実行 (<code>const slickInit = setTimeout()</code>) <ul> <li><code>.not('.slick-initialized')</code> 付き</li> </ul></li> </ol></li> <li>モーダルを閉じる時 <ol> <li><code>hidden.bs.modal</code> イベント発火(Bootstrap 独自イベント)</li> <li><code>jqueryObject.slick('unslick');</code> で <code>unslick</code> 発動、 Slick を解除</li> <li><code>clearTimeout(slickInit);</code> によって見初期化であろうとも上述「モーダルを開くボタンクリック時 2.」の動作を解除</li> <li><code>modalButtonjqueryObject.off('hidden.bs.modal');</code> で<code>hidden.bs.modal</code> イベントもバインド解除</li> </ol></li> </ul> <pre><code class="javascript">// setPosition ありで モーダル内 Slick を実行させる const $unSlickModalButton = $('#unSlickModalButton'); const unSlickModalID = $unSlickModalButton.attr('data-target'); const $unSlickModal = $(unSlickModalID); const slickInitializedClass = 'slick-initialized'; $unSlickModalButton.on('click', function () { const $unSlick = $('#unSlick'); // 0.3s 後に setPosition 付きで Slick 実行 const slickInit = setTimeout(() => { // Slick $unSlick.not('.slick-initialized').slick({ lazyLoad: 'ondemand' }); $unSlick.slick('setPosition'); }, 300); $unSlickModal.on('hidden.bs.modal', function () { // Bootstrap のモーダルを閉じるイベントが発火したら if ($unSlick.hasClass(slickInitializedClass)) { // Slick 対象要素が slick-initialized クラスを持っていたら // unslick で Slick をアンバインド $unSlick.slick('unslick'); } // setTimeout 発動前にモーダルを閉じると setTimeout が生き残っているので、すぐまたモーダルを開いたりすると Slick のバインドが2回走ったりしておかしくなるので clearTimeout する clearTimeout(slickInit); // モーダルを閉じる度に hidden.bs.modal イベントがバインドされて重複処理してしまうので閉じられたらアンバインドする $unSlickModal.off('hidden.bs.modal'); }); }); </code></pre> <p>※サンプルの「Slick 4」のケース</p> <p>ここまでやって、ようやく納得のいく挙動になりました。思ったよりも紆余曲折ありました……。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <h3 id="Slick"><a href="#Slick">Slick</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://kenwheeler.github.io/slick/">slick - the last carousel you'll ever need</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://tr.you84815.space/slick/">slickドキュメント翻訳 | slick - にほんご。</a></li> </ul> <h3 id="lazyLoad"><a href="#lazyLoad">lazyLoad</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://takblog.site/web/?p=63">「slick.js」の使い方応用編 | takblog</a></li> </ul> <h3 id="setPotion"><a href="#setPotion">setPotion</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/mimoe/items/c4f4754815525b08041e">slick.jsをモーダルなど、最初非表示のもので使うとき - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.a-in-hello.world/tab_slick.html">タブ切り替え時にslickスライダーが崩れる問題を3行で解決 | "A" In Hello,World!</a></li> </ul> <h3 id="Uncaught TypeError: Cannot read property 'add' of null"><a href="#Uncaught+TypeError%3A+Cannot+read+property+%27add%27+of+null">Uncaught TypeError: Cannot read property 'add' of null</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.proglad.tokyo/2017/08/slickjs-uncaught-typeerror-cannot-read.html">slick.js で Uncaught TypeError: Cannot read property 'add' of null のエラーがでる | Proglad</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.d-wood.com/blog/2019/02/19_11196.html">スマートフォンでのみ Slick スライダーを表示させる | deadwood</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ituki_b/items/cdca1b4914428f8e095c">slickでレスポンシブ覚書 - Qiita</a></li> </ul> arm-band tag:crieit.net,2005:PublicArticle/16250 2020-12-02T23:50:23+09:00 2020-12-21T23:35:58+09:00 https://crieit.net/posts/bootstrap-paint-in-variable-20201202 Dart Sass (@use, @forward 使用)で Bootstrap 4 の変数やマップを上書きする <p>Dart Sass (<code>@use</code>, <code>@forward</code> 使用)で Bootstrap 4 の変数・マップを上書きしたくなったので実験してみました。</p> <h2 id="今まで (node-sass / LibSass)"><a href="#%E4%BB%8A%E3%81%BE%E3%81%A7+%28node-sass+%2F+LibSass%29">今まで (node-sass / LibSass)</a></h2> <h3 id="前提"><a href="#%E5%89%8D%E6%8F%90">前提</a></h3> <pre><code class="bash"> / └ src/ ├ html/ │ └ index.html │ └ scss/ ├ assets/ │ └ bootstrap/ │ │ └ bootstrap/ │ │ └ 略 │ └ bootstrap.scss │ ├ foundation/ │ ├ _index.scss │ ├ _mixin.scss │ └ _variables.scss │ ├ layout/ │ ├ _l-footer.scss │ ├ _l-header.scss │ └ _l-main.scss │ ├ object/ │ ├ component/ │ │ └ 略 │ ├ project/ │ │ └ 略 │ └ utility/ │ └ 略 │ └ index.scss </code></pre> <p>今回の検証で使用するプロジェクトのディレクトリ構造は、このような状態とします。</p> <h3 id="src/html/index.html"><a href="#src%2Fhtml%2Findex.html">src/html/index.html</a></h3> <pre><code class="html"><div class="mt-4"> <a href="#" class="btn btn-primary m-3">プライマリーボタン</a><!-- プライマリーカラーのボタン --> <a href="#" class="btn btn-main m-3">メインボタン</a><!-- デフォルトにはないボタン --> </div> </code></pre> <p>例として、こんな HTML があったとして。</p> <h3 id="src/scss/foundation/_scss_variable.scss"><a href="#src%2Fscss%2Ffoundation%2F_scss_variable.scss">src/scss/foundation/_scss_variable.scss</a></h3> <pre><code class="scss">$theme-colors: ( /* 上書き */ primary: $own-color, /* デフォルトにないカラーの追加 */ main: $own-main-color, ); </code></pre> <p>マップの定義を上書きするコードを書きます。</p> <h3 id="src/scss/foundation/_index.scss"><a href="#src%2Fscss%2Ffoundation%2F_index.scss">src/scss/foundation/_index.scss</a></h3> <pre><code class="scss">@import "variables"; //変数(Bootstrap の変数上書きのコードあり) @import "mixin"; @import "../assets/bootstrap/bootstrap"; //bootstrap </code></pre> <p>同じ <code>foundation</code> の中に読み込み用の Scss を用意します。</p> <p>注意する点としては、「 Bootstrap の Scss を読み込むより前に、 Bootstrap の変数(マップ)を上書きするための自前の定義が書かれた Scss を読み込む」ということ。</p> <h3 id="src/scss/index.scss"><a href="#src%2Fscss%2Findex.scss">src/scss/index.scss</a></h3> <pre><code class="scss">@import "./foundation/index"; //読み込み </code></pre> <p>最後に、実際に <code>index.css</code> にコンパイルされる <code>src/scss/index.scss</code> で <code>src/scss/foundation/_index.scss</code> を読み込みます。</p> <p>今までは、これで変数の上書きができました。</p> <h2>Dart Sass での検証1 (失敗 / 単純に <code>@import</code> を <code>@use</code>, <code>@forward</code> に書き換え)</h2> <p>さて、ここで単純に今まで <code>@import</code> で記述していたのを <code>@use</code>, <code>@forward</code> に書き換えてみます。</p> <h3 id="src/scss/foundation/_scss_variable.scss"><a href="#src%2Fscss%2Ffoundation%2F_scss_variable.scss">src/scss/foundation/_scss_variable.scss</a></h3> <pre><code class="scss">$theme-colors: ( /* 上書き */ primary: $own-color, /* デフォルトにないカラーの追加 */ main: $own-main-color, ); </code></pre> <p>上書きしたい変数(マップ)の定義はそのまま。</p> <h3 id="src/scss/foundation/_index.scss"><a href="#src%2Fscss%2Ffoundation%2F_index.scss">src/scss/foundation/_index.scss</a></h3> <pre><code class="scss">@forward "scss_variables"; //変数(Bootstrap の変数上書きのコードあり) @forward "mixin"; @forward "../assets/bootstrap/bootstrap"; //bootstrap </code></pre> <p>今度は <code>@import</code> ではなく、 <code>@forward</code> に書き換えます。</p> <h3 id="src/scss/layout/_l-header.scss"><a href="#src%2Fscss%2Flayout%2F_l-header.scss">src/scss/layout/_l-header.scss</a></h3> <pre><code class="scss">@use "../foundation" as f; .l-header { .navbar-brand { &, &:link, &:visited { color: f.$own-main-color; } &:hover, &:active, &:focus { color: f.$own-main-color_l; } } } // 略 </code></pre> <p>実際に <code>src/scss/foundation/_index.scss</code> を読み込んで使用する Scss で <code>@use</code> による読み込みを行います。</p> <h3 id="src/scss/index.scss"><a href="#src%2Fscss%2Findex.scss">src/scss/index.scss</a></h3> <pre><code class="scss">@use "layout/l-header"; //_l-header.scss の中で @use を使って src/scss/foundation/_index.scss を読み込み、使用 @use "layout/l-main"; @use "layout/l-footer"; </code></pre> <p>こんな形に書き換えます。</p> <p>流れとしては、「<code>src/scss/index.scss</code> -(<code>@use</code>)-> <code>src/scss/layout/_l-header.scss</code> -(<code>@forward</code>)-> <code>src/scss/foundation/_index.scss</code>」という関係。</p> <p>この状態で Dart Sass によるコンパイルを実行すると、<code>$theme-colors</code> の変数名が重複しているのでエラーになってしまいます。</p> <pre><code class="bash">Error: src/scss/foundation/_index.scss Error: Two forwarded modules both define a variable named $theme-colors. ╷ 3 │ @forward "variables"; │ ━━━━━━━━━━━━━━━━━━━━ original @forward ... │ 5 │ @forward "../assets/bootstrap/bootstrap"; //bootstrap │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ new @forward ╵ src/scss/foundation/_index.scss 5:1 @use src/scss/layout/_l-header.scss 3:1 @use src/scss/index.scss 8:1 root stylesheet </code></pre> <p>回避するためにはいくつかの方法が考えられますが、 Bootstrap 4 の <code>@import</code> を書き換えていくのは大変な上に既存ライブラリに手を入れるのはできれば避けたいので今回は不採用。</p> <p>他の方法としては <code>with</code> を使う方法が考えられます。</p> <p>ただし、これもいくつか工夫が必要です。</p> <h2>Dart Sass での検証2 (失敗 / <code>@foward</code> ~ <code>with</code>)</h2> <p>安直に <code>with</code> を使おうかと考えましたが、構文的に <code>with</code> は <code>@use</code> でしか使えません。</p> <p>そのため、以下のような書き換えは不可です。</p> <h3 id="src/scss/foundation/_index.scss"><a href="#src%2Fscss%2Ffoundation%2F_index.scss">src/scss/foundation/_index.scss</a></h3> <pre><code class="scss">@forward "../assets/bootstrap/bootstrap" with ( /* with の中では use で読み込んだファイルのスコープは使えない */ $theme-colors: ( /* 上書き */ primary: #333, /* デフォルトにないカラーの追加 */ main: red, ), ); //bootstrap </code></pre> <h2 id="Dart Sass での検証3 (失敗 / ファイルスコープ)"><a href="#Dart+Sass+%E3%81%A7%E3%81%AE%E6%A4%9C%E8%A8%BC3+%28%E5%A4%B1%E6%95%97+%2F+%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%29">Dart Sass での検証3 (失敗 / ファイルスコープ)</a></h2> <p><code>@use</code> とセットで使うということが分かったので、今度は <code>@use</code> で読み込んでいる場所で <code>with</code> を付けたそうと思いました。</p> <h3 id="src/scss/layout/_l-header.scss"><a href="#src%2Fscss%2Flayout%2F_l-header.scss">src/scss/layout/_l-header.scss</a></h3> <pre><code class="scss">@use "../foundation" as f with ( $theme-colors: ( /* 上書き */ primary: f.$own-color, /* デフォルトにないカラーの追加 */ main: f.$own-main-color, ), ); .l-header { .navbar-brand { &, &:link, &:visited { color: f.$own-main-color; } &:hover, &:active, &:focus { color: f.$own-main-color_l; } } } // 略 </code></pre> <p>ただし、このような既述をすると以下のエラーが発生します。</p> <pre><code class="bash">Error: src/scss/layout/_l-header.scss Error: There is no module with the namespace "f". ╷ 7 │ primary: f.$own-color, │ ^^^^^^^^^^^^^ ╵ src/scss/layout/_l-header.scss 7:18 @use src/scss/index.scss 8:1 root stylesheet </code></pre> <p><code>with</code> の中では、その直前で既述した名前空間はまだ使えません。</p> <p>「独自に定義したメインカラーでプライマリーカラーを上書きしたい」というケースが多いと思われるので、 <code>with</code> で上書きするときにメインカラーが変数で指定できないのは困ります。</p> <p>そこで、独自定義の変数読み込みと Bootstrap 4 の読み込み部分を分けることにしました。</p> <h2 id="最終形"><a href="#%E6%9C%80%E7%B5%82%E5%BD%A2">最終形</a></h2> <h3 id="ディレクトリ構造"><a href="#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E9%80%A0">ディレクトリ構造</a></h3> <pre><code class="bash"> / └ src/ ├ html/ │ └ index.html │ └ scss/ ├ assets/ │ └ bootstrap/ │ │ └ bootstrap/ │ │ └ 略 │ └ bootstrap.scss │ ├ foundation/ │ ├ _index.scss │ ├ _mixin.scss │ └ _variables.scss │ ├ global/ │ └ _index.scss # 追加 │ ├ layout/ │ ├ _l-footer.scss │ ├ _l-header.scss │ └ _l-main.scss │ ├ object/ │ ├ component/ │ │ └ 略 │ ├ project/ │ │ └ 略 │ └ utility/ │ └ 略 │ └ index.scss </code></pre> <h3 id="src/scss/foundation/_scss_variable.scss"><a href="#src%2Fscss%2Ffoundation%2F_scss_variable.scss">src/scss/foundation/_scss_variable.scss</a></h3> <p>先程まであった上書き用のコードは削除。</p> <h3 id="src/scss/foundation/_index.scss"><a href="#src%2Fscss%2Ffoundation%2F_index.scss">src/scss/foundation/_index.scss</a></h3> <pre><code class="scss">@forward "variables"; @forward "mixin"; </code></pre> <p>Bootstrap 4 を読み込む <code>@forward</code> を削除。</p> <h3 id="src/scss/global/_index.scss"><a href="#src%2Fscss%2Fglobal%2F_index.scss">src/scss/global/_index.scss</a></h3> <pre><code class="scss">@forward "../assets/bootstrap/bootstrap"; //bootstrap </code></pre> <p>Bootstrap 4 を読み込む部分を切り出したのを、 <code>src/scss/global/_index.scss</code> とします。</p> <h3 id="src/scss/layout/_l-header.scss"><a href="#src%2Fscss%2Flayout%2F_l-header.scss">src/scss/layout/_l-header.scss</a></h3> <pre><code class="scss">@use "../foundation" as f; @use "../global" as g with ( $theme-colors: ( /* 上書き */ primary: f.$own-color, /* デフォルトにないカラーの追加 */ main: f.$own-main-color, ), ); .l-header { .navbar-brand { &, &:link, &:visited { color: f.$own-main-color; } &:hover, &:active, &:focus { color: f.$own-main-color_l; } } } // 略 </code></pre> <p>実際に使う部分。変数は <code>foundation</code>, Bootstrap 4 は <code>global</code> で読み込み、 <code>foundation</code> を先に読み込むことで、 <code>with</code> の中で使用できるようにしました。</p> <h3 id="src/scss/index.scss"><a href="#src%2Fscss%2Findex.scss">src/scss/index.scss</a></h3> <pre><code class="scss">@use "layout/l-header"; @use "layout/l-main"; @use "layout/l-footer"; </code></pre> <p><code>index.css</code> になる部分は読み込みだけ。</p> <p>この形にすることでようやく当初意図していた形にすることができました。</p> <h2 id="備考1"><a href="#%E5%82%99%E8%80%831">備考1</a></h2> <p>今回の方法では Bootstrap 4 の中身は一切触れない方向で実装しました。そのため、 Bootstrap 4 の中は以前として <code>@import</code> で読み込まれており、変数はグローバルスコープに定義されるようです。</p> <p>そのため、今回は <code>src/scss/layout/_l-header.scss</code> でしか <code>with</code> を使用していませんが……</p> <p><a href="https://crieit.now.sh/upload_images/3415ce329faf1c572815489dd4a732555fc7a9865a365.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3415ce329faf1c572815489dd4a732555fc7a9865a365.jpg?mw=700" alt="デフォルト状態のサンプル" /></a></p> <p>デフォルト状態ではこのような状態になります(プライマリーカラーがデフォルト、右側はデフォルト状態では存在しない <code>btn-main</code> クラスが付与されているため背景色なし)。</p> <p><a href="https://crieit.now.sh/upload_images/bf9bc07305297db9738d5598f4d950bd5fc7a98f8206f.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/bf9bc07305297db9738d5598f4d950bd5fc7a98f8206f.jpg?mw=700" alt="適用状態のサンプル" /></a></p> <p><code>src/scss/layout/_l-header.scss</code> に <code>with</code> を適用しただけで、メイン部分のボタンも影響を受けます。今回の場合は「全体で色を変更したい」のでこれで良いのですが、 <code>@use</code> や <code>@forward</code> の位置付けからすると役割を発揮できていない状態なので微妙なところ。</p> <p>今後、 Bootstrap が <code>@use</code>, <code>@forward</code> に変更した場合にこの辺りの挙動は変わると思われます。</p> <p>※ちなみに、v5.0.0-alpha3 でもまだ <code>@import</code> でした。</p> <h2 id="備考2"><a href="#%E5%82%99%E8%80%832">備考2</a></h2> <p>備考1と関連しますが、グローバルスコープに定義されるということは……</p> <h3 id="src/scss/layout/_l-header.scss"><a href="#src%2Fscss%2Flayout%2F_l-header.scss">src/scss/layout/_l-header.scss</a></h3> <pre><code class="scss">@use "../foundation" as f; @use "../global" as g; .l-header { .navbar-brand { &, &:link, &:visited { color: f.$own-main-color; } &:hover, &:active, &:focus { color: f.$own-main-color_l; } } } // 略 </code></pre> <p><code>src/scss/layout/_l-header.scss</code> は普通に <code>src/scss/global/_index.scss</code> を読み込み……</p> <h3 id="src/scss/layout/_l-main.scss"><a href="#src%2Fscss%2Flayout%2F_l-main.scss">src/scss/layout/_l-main.scss</a></h3> <pre><code class="scss">@use "../foundation" as f; @use "../global" as g with ( $theme-colors: ( /* 上書き */ primary: f.$own-color, /* デフォルトにないカラーの追加 */ main: f.$own-main-color, ), ); .l-main { background-color: f.$own-bg-color; color: f.$own-color; .btn-main { color: f.$own-color; } } // 略 </code></pre> <p><code>src/scss/layout/_l-header.scss</code> の後に読み込まれる <code>src/scss/layout/_l-main.scss</code> で <code>with</code> を使用した場合、以下のエラーが発生します。一度読み込んだモジュールが <code>with</code> 使用で再度読み込まれている、という旨のエラーですね。</p> <pre><code class="bash">Error: This module was already loaded, so it can't be configured using "with". ┌──> src/scss/layout/_l-main.scss 4 │ ┌ @use "../global" as g with ( 5 │ │ $theme-colors: ( 6 │ │ /* 上書き */ 7 │ │ primary: f.$own-color, 8 │ │ /* デフォルトにないカラーの追加 */ 9 │ │ main: f.$own-main-color, 10│ │ ), 11│ │ ); │ └─^ new load ╵ ┌──> src/scss/layout/_l-header.scss 4 │ @use "../global" as g; │ ━━━━━━━━━━━━━━━━━━━━━ original load ╵ src/scss/layout/_l-main.scss 4:1 @use src/scss/index.scss 9:1 root stylesheet Error: src/scss/layout/_l-main.scss Error: This module was already loaded, so it can't be configured using "with". ┌──> src/scss/layout/_l-main.scss 4 │ ┌ @use "../global" as g with ( 5 │ │ $theme-colors: ( 6 │ │ /* 上書き */ 7 │ │ primary: f.$own-color, 8 │ │ /* デフォルトにないカラーの追加 */ 9 │ │ main: f.$own-main-color, 10│ │ ), 11│ │ ); │ └─^ new load ╵ ┌──> src/scss/layout/_l-header.scss 4 │ @use "../global" as g; │ ━━━━━━━━━━━━━━━━━━━━━ original load ╵ src/scss/layout/_l-main.scss 4:1 @use src/scss/index.scss 9:1 root stylesheet </code></pre> <p>そのため、 <code>with</code> を使用するならば全体を通して <code>src/scss/global/_index.scss</code> が最初に <code>@use</code> で読み込まれる部分に既述する必要があります。</p> <hr /> <p>以上、知らないと全体的に嵌まり所が多い感じだったのでメモしておきます。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <h3 id="with"><a href="#with">with</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.boel.co.jp/tips/vol114/">Sassの新しいモジュールシステム | BOEL Inc. | ブランディング&デザインファーム</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://kojika17.com/2020/05/next-generation-sass-module-system.html">Sassを@importから@useに置き換えるための手引き | Web Design KOJIKA17</a></li> </ul> <h3 id="今までのやり方 (参考)"><a href="#%E4%BB%8A%E3%81%BE%E3%81%A7%E3%81%AE%E3%82%84%E3%82%8A%E6%96%B9+%28%E5%8F%82%E8%80%83%29">今までのやり方 (参考)</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://homupedia.com/bootstrap4-how-to-customize-theme.html">Bootstrap4のテーマをカスタマイズする3つの簡単な方法 | ホムペディア</a></li> </ul> <h3 id="Bootstrap (v5.0.0-alpha3)"><a href="#Bootstrap+%28v5.0.0-alpha3%29">Bootstrap (v5.0.0-alpha3)</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/twbs/bootstrap/tree/v5.0.0-alpha3">twbs/bootstrap at v5.0.0-alpha3</a></li> </ul> arm-band