tag:crieit.net,2005:https://crieit.net/tags/Bootstrap/feed
「Bootstrap」の記事 - Crieit
Crieitでタグ「Bootstrap」に投稿された最近の記事
2022-04-30T00:03:10+09:00
https://crieit.net/tags/Bootstrap/feed
tag:crieit.net,2005:PublicArticle/18181
2022-04-30T00:03:10+09:00
2022-04-30T00:03:10+09:00
https://crieit.net/posts/omit-jquery-note-20220502
脱jQuery メモ
<h2 id="経緯"><a href="#%E7%B5%8C%E7%B7%AF">経緯</a></h2>
<p>Bootstrap でブレークポイント未満(スマホ時)のナビゲーションリンクにアンカーリンクがある場合、アンカーリンクをタップしてもナビゲーションリンクのメニューが開いたままアンカーリンクへ遷移するので、それを制御するコードを自前で書いていました。その部分を脱 jQuery したのでメモしておきます。</p>
<h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2>
<h3 id="jQuery"><a href="#jQuery">jQuery</a></h3>
<p>まずは Bootstrap 4 までの jQuery でのコード。</p>
<pre><code class="js">// ナビゲーションバー
const $navbar = $('#navbar');
// ブランド名とドロップダウンコンポーネント以外のナビゲーションリスト
$navbar.find('.navbar-brand, .nav-item:not(.dropdown) a, .dropdown-item').on('click', function (e) {
// 略
const navBarListID = 'navbarList';
if ($(e.currentTarget).closest('#' + navBarListID).length > 0) {
let breakpoint = 0;
if (/(^|\s)navbar-expand-(\S*)/g.test($navbar.children('.navbar').prop('class'))) {
switch (RegExp.$2) {
case 'sm':
breakpoint = 576;
break;
case 'md':
breakpoint = 768;
break;
case 'lg':
breakpoint = 992;
break;
case 'xl':
breakpoint = 1200;
break;
default:
breakpoint = 0;
break;
}
}
if ($(window).outerWidth() < breakpoint && !$navbar.find('.navbar-toggler[data-target="#' + navBarListID + '"]').hasClass('collapsed')) {
// 現在の表示がハンバーガーメニューの場合
$navbar.find('.navbar-toggler[data-target="#' + navBarListID + '"]').trigger('click'); // 移動したらハンバーガーを折りたたむ
} else if ($(e.currentTarget).hasClass('dropdown-item') && $(e.currentTarget).closest('.dropdown').hasClass('show')) {
// 現在の表示がハンバーガーメニューではなく、ドロップダウン内のメニューをクリックした場合
$(e.currentTarget).closest('.dropdown').trigger('click'); // 移動したらドロップダウンを折りたたむ
}
}
return false;
}
};
</code></pre>
<p>ざっくりこんなコードでした。やっていることとしては</p>
<ul>
<li>ブランド(<code>.navbar-brand</code>), ドロップダウンではないナビゲーションリンク(<code>.nav-item:not(.dropdown) a</code>, ドロップダウン内のリンク(<code>.dropdown-item</code>) がクリックされた場合
<ul>
<li>クリック(タップ)された要素の直近の祖先要素に <code>#navbarList</code> の id属性 がある要素が存在する場合
<ul>
<li>ナビゲーションバー要素のクラスにある <code>.navbar-expand-XX</code> のクラスのブレークポイントの文字列を取得してブレークポイント値をセット</li>
<li>その値と現在のウィンドウの幅を比較してウインドウ幅の方が小さい (=ハンバーガーメニューに表示が切り替わっている) 、かつ <code>.navbar-toggler</code> 要素が <code>.collapsed</code> のclass属性 を持っている (=メニュー展開) 場合
<ul>
<li>ハンバーガーメニューを折りたたむ (ハンバーガーアイコンを1度クリックする)</li>
</ul></li>
<li>またはクリックされた要素が <code>.dropdown-item</code> class属性 を持っている (=ドロップダウンメニュー) 、かつ直近の祖先要素で <code>.dropdown</code> class属性 を持つ要素が <code>.show</code> class属性 を持っている (=ドロップダウンが開かれている) 場合
<ul>
<li>ドロップダウン要素を折りたたむ</li>
</ul></li>
</ul></li>
</ul></li>
</ul>
<p>という挙動。</p>
<h3 id="プレーン JavaScript"><a href="#%E3%83%97%E3%83%AC%E3%83%BC%E3%83%B3+JavaScript">プレーン JavaScript</a></h3>
<p>これを Bootstrap 5 対応でプレーンな JavaScirpt に書き換え。</p>
<pre><code class="js">const navbar = document.querySelector('#navbar');
const navBarListID = 'navbarList';
if (
typeof e.currentTarget.closest(`#${navBarListID}`) !== 'undefined'
&& e.currentTarget.closest(`#${navBarListID}`) !== null
) {
let breakpoint = 0;
if (/(^|\s)navbar-expand-(\S*)/g.test(navbar.className)) {
switch (RegExp.$2) {
case 'sm':
breakpoint = 576;
break;
case 'md':
breakpoint = 768;
break;
case 'lg':
breakpoint = 992;
break;
case 'xl':
breakpoint = 1200;
break;
case 'xxl':
breakpoint = 1400;
break;
default:
breakpoint = 0;
break;
}
}
if (window.innerWidth < breakpoint) {
// ブレークポイント未満の幅のとき
const navbarTogglers = navbar.querySelectorAll(`.navbar-toggler[data-bs-target="#${navBarListID}"]`);
navbarTogglers.forEach((navbarToggler) => {
if(!navbarToggler.classList.contains('collapsed')) {
// 現在の表示がハンバーガーメニューの場合、
// 移動したらハンバーガーを折りたたむ
navbarToggler.dispatchEvent(new Event('click'));
}
else if(
e.currentTarget.classList.contains('dropdown-item')
&& e.currentTarget.closest('.dropdown').classList.contains('show')
) {
// 現在の表示がハンバーガーメニューではなく、ドロップダウン内のメニューをクリックした場合
// 移動したらドロップダウンを折りたたむ
e.currentTarget.closest('.dropdown').dispatchEvent(new Event('click'));
}
});
}
}
</code></pre>
<p>やっていることは大体同じです。ただし、いくつか置き換えが必要な部分があったので以下その点について触れていきます。</p>
<h2 id="置き換えた部分"><a href="#%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%9F%E9%83%A8%E5%88%86">置き換えた部分</a></h2>
<h3 id="複数のクラス指定で要素を取得"><a href="#%E8%A4%87%E6%95%B0%E3%81%AE%E3%82%AF%E3%83%A9%E3%82%B9%E6%8C%87%E5%AE%9A%E3%81%A7%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97">複数のクラス指定で要素を取得</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://1-notes.com/javascript-multiple-with-getelementsbyclassname/">JavaScript | getElementsByClassNameで複数のclass名を指定して取得する方法 | ONE NOTES</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/none-jquery-find/">脱jQuery .find() | q-Az</a></li>
</ul>
<pre><code class="js">const elms = document.querySelectorAll('.hoge, .fuga');
</code></pre>
<p>jQuery のようにカンマ区切りで <code>document.querySelectorAll()</code> でOK。</p>
<h3 id=".on()"><a href="#.on%28%29">.on()</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/without-jquery-on-off/">脱jQuery .on() .off() | q-Az</a></li>
</ul>
<p>普通に <code>.addEventListener('eventName', function)</code> でOK。</p>
<h3 id="複数の要素に対するイベントハンドラ"><a href="#%E8%A4%87%E6%95%B0%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9">複数の要素に対するイベントハンドラ</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://www.mitsue.co.jp/knowledge/blog/frontend/201805/24_0917.html">どうして!?document.querySelectorAll(selector).addEventListener()が動かないわけ | フロントエンドBlog | ミツエーリンクス</a></li>
</ul>
<pre><code class="js">const elms = document.querySelectorAll('.hoge, .fuga');
elms.forEach(elm => {
elm.addEventListener('click', process);
},
false);
</code></pre>
<p><code>.querySelectorAll()</code> で取得した要素を <code>.forEach()</code> で反復処理させます。</p>
<h3 id="親要素・祖先要素"><a href="#%E8%A6%AA%E8%A6%81%E7%B4%A0%E3%83%BB%E7%A5%96%E5%85%88%E8%A6%81%E7%B4%A0">親要素・祖先要素</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://yuyauver98.me/js-parentnode-closest/">【Javascript】親要素や祖先要素を取得する方法 parentNode\/closest | ゆうやの雑記ブログ</a></li>
</ul>
<p>普通に <code>.closet('.parent')</code> 。</p>
<h3 id="子要素"><a href="#%E5%AD%90%E8%A6%81%E7%B4%A0">子要素</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://webty.jp/staffblog/production/post-1745/">脱jQuery!DOM要素取得コードの素のJavaScriptへの書き換え │ Webty Staff Blog</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://the-zombis.sakura.ne.jp/wp/blog/2019/09/16/post-4014/">【JavaScript】脱JQuery!?メソッドを比べてみた!要素取得編 - Web.fla</a></li>
</ul>
<p>jQuery の <code>.children('selectorName')</code> は一応プレーンな JavaScript にも <code>.children</code>プロパティ がある模様。</p>
<p>ただし、 jQuery のように <code>.children</code> へセレクタ指定はできなさそうなので、この部分は HTML のクラスを子要素の方に付けることで回避しました。</p>
<h3 id="クラス名全てを取得"><a href="#%E3%82%AF%E3%83%A9%E3%82%B9%E5%90%8D%E5%85%A8%E3%81%A6%E3%82%92%E5%8F%96%E5%BE%97">クラス名全てを取得</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://shanabrian.com/web/javascript/get-class.php">クラス名(class属性の値)をすべて取得 | JavaScript逆引き | Webサイト制作支援 | ShanaBrian Website</a></li>
</ul>
<p>jQuery では <code>.prop('class')</code> としていたところですが、 <code>.className</code> でOK。</p>
<h3 id="子孫要素の中から探す"><a href="#%E5%AD%90%E5%AD%AB%E8%A6%81%E7%B4%A0%E3%81%AE%E4%B8%AD%E3%81%8B%E3%82%89%E6%8E%A2%E3%81%99">子孫要素の中から探す</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/none-jquery-find/">脱jQuery .find() | q-Az</a></li>
</ul>
<p>jQueryでは <code>.find()</code> だったところを、 <code>elm.querySelector('selectorName')</code> と指定すればOK。</p>
<h3 id="ウインドウ幅"><a href="#%E3%82%A6%E3%82%A4%E3%83%B3%E3%83%89%E3%82%A6%E5%B9%85">ウインドウ幅</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://wemo.tech/470">スクリーン・ウインドウ・画面サイズをjavascriptで取得する方法まとめ | WEMO</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/without-jquery-innerheight-width-outerheight-width/">脱jQuery .innerHeight() .innerWidth() .outerHeight() .outerWidth() | q-Az</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://ja.javascript.info/size-and-scroll-window">ウィンドウサイズとスクローリング</a></li>
</ul>
<p>jQuery で <code>$(window).outerWidth()</code> としてたところを、今回の用途では <code>window.innerWidth</code> へ置き換えました。</p>
<h3 id="クラスの存在チェック"><a href="#%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E5%AD%98%E5%9C%A8%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF">クラスの存在チェック</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/none-jquery-addclass-has-remove-toggle/">脱jQuery .addClass() .hasClass() .removeClass() .toggleClass() | q-Az</a></li>
</ul>
<p>jQuery では <code>.hasClass('className')</code> だったところを、 <code>.classList.contains('className')</code> へ置き換え。</p>
<h3 id="イベントハンドラの指定とイベント発火要素の取得"><a href="#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9%E3%81%AE%E6%8C%87%E5%AE%9A%E3%81%A8%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E7%99%BA%E7%81%AB%E8%A6%81%E7%B4%A0%E3%81%AE%E5%8F%96%E5%BE%97">イベントハンドラの指定とイベント発火要素の取得</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://note.com/yamanoborer/n/n2e4cc40328b7">【JavaScript】addEventListenerで関数に引数をわたす|北の南|note</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://www.mitsue.co.jp/knowledge/blog/frontend/201912/02_0000.html">脱jQueryに向けた第一歩 | フロントエンドBlog | ミツエーリンクス</a></li>
</ul>
<p>普通に関数指定で <code>elm.addEventListener('click', hoge(elm), false)</code> と書いてしまっていました。</p>
<p>しかし、参考記事に拠るとこれだと<strong>その関数の実行結果</strong>が渡されるとのこと。しかもイベント発火時ではなく該当コード読み込み時に実行されてしまうため、挙動がおかしくなってしまいます。</p>
<p>これについては第二引数はオブジェクト (または JavaScript の純粋な関数) なので <code>elm.addEventListener('click', hoge, false)</code> と関数名だけにしなければならず、その通りに書けばOK。</p>
<p>一方、 <code>const hoge = (e) => { /* 処理 */ };</code> で普通にイベントは渡ってくるので、「クリックされた要素」をイベントハンドラ内で利用したい場合は <code>e.currentTarget</code> とすれば問題ないですね。</p>
<p>最初これに気付かずしばらく嵌まっていました。参考記事に感謝。</p>
<h3 id="イベント発火"><a href="#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E7%99%BA%E7%81%AB">イベント発火</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://mae.chab.in/archives/2821#post2821-5">jQueryを使わない書き方 ajax, each, trigger, on\/off, extend, deferred, animate, css編 | maesblog</a></li>
</ul>
<p>イベントがクラスで渡す必要がありますがほぼ同じで、 <code>elm.dispatchEvent(new Event('click'))</code> とすればOK。</p>
<hr />
<p>このような形でガシガシ書き換えていけば大きな問題はなさそうです。</p>
<p>とはいえ、この置き換えは地味にアンカーリンクへのスクロールアニメーションを <code>scroll-behavior: smooth;</code> に移行したおかげで上述以外の大半の JS を破棄しても問題ないと判断できたのが非常に大きいですね。そうでなければもっと大きなボリュームと対峙する必要がありました……。</p>
<p>しかも <code>Scroll-margin-top</code> で上部固定ナビゲーションバーの裏側にアンカーリンクが隠れてしまう問題を回避できる、というのも JS コードを削減できた要因の一つなので、この2つのプロパティは個人的にかなり神がかっていると感じます。細かいイージングは犠牲になりますが、今回は全然目を瞑ることができるレベルなので良しとします。</p>
<h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2>
<h3 id="複数のクラス指定で要素を取得"><a href="#%E8%A4%87%E6%95%B0%E3%81%AE%E3%82%AF%E3%83%A9%E3%82%B9%E6%8C%87%E5%AE%9A%E3%81%A7%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97">複数のクラス指定で要素を取得</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://1-notes.com/javascript-multiple-with-getelementsbyclassname/">JavaScript | getElementsByClassNameで複数のclass名を指定して取得する方法 | ONE NOTES</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/none-jquery-find/">脱jQuery .find() | q-Az</a></li>
</ul>
<h3 id=".on()"><a href="#.on%28%29">.on()</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/without-jquery-on-off/">脱jQuery .on() .off() | q-Az</a></li>
</ul>
<h3 id="複数の要素に対するイベントハンドラ"><a href="#%E8%A4%87%E6%95%B0%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9">複数の要素に対するイベントハンドラ</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://www.mitsue.co.jp/knowledge/blog/frontend/201805/24_0917.html">どうして!?document.querySelectorAll(selector).addEventListener()が動かないわけ | フロントエンドBlog | ミツエーリンクス</a></li>
</ul>
<h3 id="親要素・祖先要素"><a href="#%E8%A6%AA%E8%A6%81%E7%B4%A0%E3%83%BB%E7%A5%96%E5%85%88%E8%A6%81%E7%B4%A0">親要素・祖先要素</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://yuyauver98.me/js-parentnode-closest/">【Javascript】親要素や祖先要素を取得する方法 parentNode\/closest | ゆうやの雑記ブログ</a></li>
</ul>
<h3 id="子要素"><a href="#%E5%AD%90%E8%A6%81%E7%B4%A0">子要素</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://webty.jp/staffblog/production/post-1745/">脱jQuery!DOM要素取得コードの素のJavaScriptへの書き換え │ Webty Staff Blog</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://the-zombis.sakura.ne.jp/wp/blog/2019/09/16/post-4014/">【JavaScript】脱JQuery!?メソッドを比べてみた!要素取得編 - Web.fla</a></li>
</ul>
<h3 id="クラス名全てを取得"><a href="#%E3%82%AF%E3%83%A9%E3%82%B9%E5%90%8D%E5%85%A8%E3%81%A6%E3%82%92%E5%8F%96%E5%BE%97">クラス名全てを取得</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://shanabrian.com/web/javascript/get-class.php">クラス名(class属性の値)をすべて取得 | JavaScript逆引き | Webサイト制作支援 | ShanaBrian Website</a></li>
</ul>
<h3 id="子孫要素の中から探す"><a href="#%E5%AD%90%E5%AD%AB%E8%A6%81%E7%B4%A0%E3%81%AE%E4%B8%AD%E3%81%8B%E3%82%89%E6%8E%A2%E3%81%99">子孫要素の中から探す</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/none-jquery-find/">脱jQuery .find() | q-Az</a></li>
</ul>
<h3 id="ウインドウ幅"><a href="#%E3%82%A6%E3%82%A4%E3%83%B3%E3%83%89%E3%82%A6%E5%B9%85">ウインドウ幅</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://wemo.tech/470">スクリーン・ウインドウ・画面サイズをjavascriptで取得する方法まとめ | WEMO</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/without-jquery-innerheight-width-outerheight-width/">脱jQuery .innerHeight() .innerWidth() .outerHeight() .outerWidth() | q-Az</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://ja.javascript.info/size-and-scroll-window">ウィンドウサイズとスクローリング</a></li>
</ul>
<h3 id="クラスの存在チェック"><a href="#%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E5%AD%98%E5%9C%A8%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF">クラスの存在チェック</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://q-az.net/none-jquery-addclass-has-remove-toggle/">脱jQuery .addClass() .hasClass() .removeClass() .toggleClass() | q-Az</a></li>
</ul>
<h3 id="イベントハンドラの指定とイベント発火要素の取得"><a href="#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9%E3%81%AE%E6%8C%87%E5%AE%9A%E3%81%A8%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E7%99%BA%E7%81%AB%E8%A6%81%E7%B4%A0%E3%81%AE%E5%8F%96%E5%BE%97">イベントハンドラの指定とイベント発火要素の取得</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://note.com/yamanoborer/n/n2e4cc40328b7">【JavaScript】addEventListenerで関数に引数をわたす|北の南|note</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://www.mitsue.co.jp/knowledge/blog/frontend/201912/02_0000.html">脱jQueryに向けた第一歩 | フロントエンドBlog | ミツエーリンクス</a></li>
</ul>
<h3 id="イベント発火"><a href="#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E7%99%BA%E7%81%AB">イベント発火</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://mae.chab.in/archives/2821#post2821-5">jQueryを使わない書き方 ajax, each, trigger, on\/off, extend, deferred, animate, css編 | maesblog</a></li>
</ul>
<h3 id="その他"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96">その他</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/nightyknite/items/668c112c40931515ed67">JQueryをVanilla JSに緩やかに置き換える - Qiita</a></li>
</ul>
<h3 id="Bootstrap 5 のブレークポイント"><a href="#Bootstrap+5+%E3%81%AE%E3%83%96%E3%83%AC%E3%83%BC%E3%82%AF%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88">Bootstrap 5 のブレークポイント</a></h3>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://bootstrap-guide.com/components/navbar">ナビゲーションバー~Bootstrap5設置ガイド</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://bootstrap-guide.com/layout/breakpoints">ブレークポイント~Bootstrap5設置ガイド</a></li>
</ul>
arm-band
tag:crieit.net,2005:PublicArticle/17979
2022-02-06T21:41:13+09:00
2022-02-06T21:41:13+09:00
https://crieit.net/posts/bootstrap5-paint-in-variable-20220206
Bootstrap 5 の変数やマップを上書きする
<p>Bootstrap 5 が去年5月にリリースされてからかなり経ってしまいましたが、 4 とは変数の上書きや追加の方法が変わっていると知ったので実験をしてみました。</p>
<h2 id="検証結果"><a href="#%E6%A4%9C%E8%A8%BC%E7%B5%90%E6%9E%9C">検証結果</a></h2>
<ul>
<li>ページ: <a target="_blank" rel="nofollow noopener" href="https://arm-band.github.io/test_bootstrap5_blackout/">Black in me - Black! - Blackout</a></li>
<li>リポジトリ: <a target="_blank" rel="nofollow noopener" href="https://github.com/arm-band/test_bootstrap5_blackout">GitHub - arm-band\/test_bootstrap5_blackout: Bootstrap5 の変数を上書きできるかテスト。</a></li>
</ul>
<h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2>
<h3 id="ディレクトリ (関連する部分のみ)"><a href="#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA+%28%E9%96%A2%E9%80%A3%E3%81%99%E3%82%8B%E9%83%A8%E5%88%86%E3%81%AE%E3%81%BF%29">ディレクトリ (関連する部分のみ)</a></h3>
<pre><code> /
└ src/
└ scss/
└ foundation/
├ bootstrap/
│ └ _import.scss
│
├ _append.scss
├ _index.scss
├ _mixin.scss
└ _variables.scss
</code></pre>
<p>ひとまずこんな形に。今回はデモ用なので <code>global</code>ディレクトリ を用意しませんでしたが、実際には <code>global</code>ディレクトリ で運用しようかな、と思います。</p>
<h3 id="src/scss/foundation/bootstrap/_import.scss"><a href="#src%2Fscss%2Ffoundation%2Fbootstrap%2F_import.scss">src/scss/foundation/bootstrap/_import.scss</a></h3>
<pre><code class="scss">@charset "utf-8";
// color custom (override)
@import "../variables";
// Configuration
@import "node_modules/bootstrap/scss/functions";
@import "node_modules/bootstrap/scss/variables";
@import "node_modules/bootstrap/scss/mixins";
@import "node_modules/bootstrap/scss/utilities";
// color custom (add to map)
@import "../append";
// Layout & components
@import "node_modules/bootstrap/scss/root";
@import "node_modules/bootstrap/scss/reboot";
// 略
</code></pre>
<p>元々の Bootstrap 5 の <code>bootstrap/scss/bootstrap.scss</code> のパスを調整したコピーを用意。</p>
<p>このコピーに2箇所手を加えます。</p>
<ol>
<li>Configuration の読み込みの<strong>前</strong>に <code>src/scss/foundation/_variables.scss</code> を読み込む</li>
<li>Configuration の読み込みの<strong>後</strong>に <code>src/scss/foundation/_append.scss</code> を読み込む</li>
</ol>
<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">@charset "utf-8";
//@forward "variables";
@forward "bootstrap/import";
@forward "mixin";
</code></pre>
<p>元々同じディレクトリにあった <code>_variable.scss</code> の読み込みを削除。これは先の <code>src/scss/foundation/bootstrap/_import.scss</code> で <code>_variable.scss</code> を読み込んでいるためです。</p>
<h3 id="src/scss/foundation/_variables.scss (既存変数の上書き)"><a href="#src%2Fscss%2Ffoundation%2F_variables.scss+%28%E6%97%A2%E5%AD%98%E5%A4%89%E6%95%B0%E3%81%AE%E4%B8%8A%E6%9B%B8%E3%81%8D%29">src/scss/foundation/_variables.scss (既存変数の上書き)</a></h3>
<pre><code class="scss">@charset "utf-8";
@use "sass:color";
$m-color: #333;
$m-main-color: #666;
$m-main-color_d: color.scale($m-main-color, $lightness: -8%);
$m-main-color_l: color.scale($m-main-color, $lightness: 8%);
$m-accent-color: color.adjust($m-main-color, $hue: 150deg);
$m-link-color: color.adjust($m-main-color, $hue: 210deg);
$m-link-v-color: color.scale($m-link-color, $lightness: -8%);
$m-link-ha-color: color.scale($m-link-color, $lightness: 8%);
$m-bg-color: #fff;
// override
$primary: $m-main-color;
$secondary: $m-bg-color;
</code></pre>
<p>変数宣言。前半は自分用の変数なのでお好みで。ただし <code>$link-color</code> 等は Bootstrap 側の変数とバッティングするのでそういった変数名は避けます。</p>
<p>その上で、今回は最後の部分に Bootstrap5 の変数の値を上書きするための <code>$primary</code> や <code>$secondary</code> を用意。このように Bootstrap 5 の変数より前に定義することで既存変数の上書きができます。</p>
<h3 id="src/scss/foundation/_append.scss"><a href="#src%2Fscss%2Ffoundation%2F_append.scss">src/scss/foundation/_append.scss</a></h3>
<pre><code class="scss">@charset "utf-8";
$custom-colors: (
"blackout": #000
);
// blackout をマップ $theme-colors に追加
$theme-colors: map-merge($theme-colors, $custom-colors);
</code></pre>
<p>一方、マップ <code>$theme-colors</code> に新たな要素を追加するためには先程の変数上書きとは異なり、 Bootstrap 5 の変数等の後でマップに追加する必要があります。</p>
<p>……確かに <code>map-merge</code> で合体するマップを指定する都合上、予め指定するマップが存在していなければならないですよね。</p>
<p>これで既存のマップ <code>$theme-colors</code> に要素 <code>blackout</code> を追加できました。</p>
<h2 id="結果"><a href="#%E7%B5%90%E6%9E%9C">結果</a></h2>
<p><a href="https://crieit.now.sh/upload_images/d1a0f6ba9f3afd27485f516c65bd2f6d61ffc13a60151.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d1a0f6ba9f3afd27485f516c65bd2f6d61ffc13a60151.jpg?mw=700" alt="ボタンの色を上書きしたり追加したり" /></a></p>
<p>Bootstrap のボタンで検証。左から2つの <code>primary</code> と <code>secondary</code> は上書きしたのでカスタマイズした色に、3つ目はオリジナルの <code>blackout</code> です。4つ目以降はデフォルトの <code>success</code> や <code>info</code> 等が続いており、上述のようにマップに一部の色のみを指定して <code>map-merge</code> しても他の色が影響を受けていないことを確認しました。</p>
<p>これで大丈夫そうですね。</p>
<h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://bootstrap-guide.com/customize/sass">Sassのカスタマイズ~Bootstrap5設置ガイド</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://getbootstrap.jp/docs/5.0/customize/sass/">Sass · Bootstrap v5.0</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/shouchida/items/2c3fc4586de66a067dfd">Bootstrap(v5)をカスタマイズして必要な機能だけ利用する - Qiita</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/67592367/unable-to-customize-bootstrap-5-theme-colors">reactjs - Unable to customize Bootstrap 5 theme colors - Stack Overflow</a></li>
</ul>
arm-band
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
tag:crieit.net,2005:PublicArticle/16242
2020-11-27T23:04:34+09:00
2020-11-27T23:04:34+09:00
https://crieit.net/posts/Next-js-Bootstrap-Sticky-Footer
Next.jsでBootstrapのSticky Footerを利用する
<p>BootstrapにはSticky Footerのデモがあります。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://v5.getbootstrap.jp/docs/5.0/examples/sticky-footer/">https://v5.getbootstrap.jp/docs/5.0/examples/sticky-footer/</a></p>
<p>昔はCSSを使っていましたが、現在はBootstrapのユーティリティを利用してクラス指定だけで実現しています。そのため適切にクラスを指定していけばよいだけなのですが、Next.jsのようなフレームワークの場合は各HTMLタグがデフォルトでは編集できなかったり、配置が決まっていたりするためなかなか思うように行きません。</p>
<p>実際に対応した方法を書いておきます。</p>
<h2 id="_document.jsを作成する"><a href="#_document.js%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">_document.jsを作成する</a></h2>
<p>まずは全体のHTMLを編集するための _document.js というファイルを作ります。TypeScriptの場合は _document.tsx です。_app.js と同様、pagesの直下に配置します。</p>
<p>中身は公式で解説しているとおりです。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://nextjs.org/docs/advanced-features/custom-document">Advanced Features: Custom <code>Document</code> | Next.js</a></p>
<h2 id="Sticky Footer用のクラスを指定していく"><a href="#Sticky+Footer%E7%94%A8%E3%81%AE%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%A6%E3%81%84%E3%81%8F">Sticky Footer用のクラスを指定していく</a></h2>
<p>次にどんどんクラスを指定していきます。まずはhtmlとbodyタグに高さ100%のクラス <code>h-100</code> を指定します。 _document.js 内です。</p>
<pre><code class="jsx"> return (
<Html lang="ja" className="h-100">
<Head />
<body className="h-100">
<Main />
<NextScript />
</body>
</Html>
)
</code></pre>
<p>次にフッター以外のメインコンテンツを下記のタグで囲みます。ここからは _document.js ではなく各ページもしくは共通レイアウトに設定します。</p>
<pre><code class="html"> <main className="flex-shrink-0">
メインコンテンツ
</main>
</code></pre>
<p>そしてその下にSticky Footerを配置します。</p>
<pre><code class="html"><footer className="footer mt-auto py-3 bg-light">
<div className="container">
<span className="text-muted">フッターのコンテンツをここに置きます。</span>
</div>
</footer>
</code></pre>
<h2 id="Next.js用の対応"><a href="#Next.js%E7%94%A8%E3%81%AE%E5%AF%BE%E5%BF%9C">Next.js用の対応</a></h2>
<p>これで一通りBootstrapのデモと同じ設定は完了です。しかし、Next.jsの場合は下記のように <code>__next</code> というidのdivが全体を囲んでしまっています。そのためデモと同様にbodyに <code>d-flex flex-column</code> を指定しても全てそのdivに反映されてしまい、メインコンテンツとフッターが正常に動作しません。そのためbodyは <code>h-100</code> だけにしていました。</p>
<p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5fc106aea8470.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5fc106aea8470.png?mw=700" alt="" /></a></p>
<p>そのため、ここだけはCSSで指定してあげます。</p>
<pre><code class="css">#__next {
display: flex;
flex-direction: column;
height: 100%;
}
</code></pre>
<p>自動挿入されている要素ですのでidなど、仕様が変わったら適宜調整が必要です。</p>
だら@Crieit開発者
tag:crieit.net,2005:PublicArticle/16119
2020-10-09T21:07:03+09:00
2020-10-12T10:12:06+09:00
https://crieit.net/posts/react-autosuggest-Bootstrap
react-autosuggestをBootstrapで使う
<p>react-autosuggestはReactでオートコンプリートを実装できるライブラリ。しかしこれはデザインがない。自分で作るか、多分デモのやつを使う必要があるが、Bootstrapを使っている場合はそれのDropdownをそのまま使いたい。</p>
<p>その場合はthemeプロパティで使うクラスを指定できる。下記のように設定すればOKっぽい</p>
<pre><code class="jsx"> <Autosuggest
theme=<span>{</span><span>{</span>
container: 'autosuggest',
input: 'form-control',
suggestionsContainer: 'dropdown open',
suggestionsList: `dropdown-menu ${items.length ? 'show' : ''}`,
suggestion: '',
suggestionFocused: 'active'
<span>}</span><span>}</span>
</code></pre>
<p>あとはrenderSuggestionsも調整。</p>
<pre><code class="jsx">const renderItem = (item: Item) => (
<a className="dropdown-item" href="#">
{item.name}
</a>
)
</code></pre>
<p>下記で話題に上がっている(最初の方に古くてちゃんと動かないのもあるのでご注意)。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/moroshko/react-autosuggest/issues/64">https://github.com/moroshko/react-autosuggest/issues/64</a></p>
だら@Crieit開発者
tag:crieit.net,2005:PublicArticle/15356
2019-08-27T18:28:49+09:00
2019-08-27T18:30:00+09:00
https://crieit.net/posts/CSS-Bluma-Vuetify-Element-Bootstrap-etc
CSSフレームワークのブレイクポイントを比べて(Bluma, Vuetify, Element, Bootstrap, etc..)
<p>いま開発している<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読用の読書管理アプリ</a>では、CSSフレームワークに<a target="_blank" rel="nofollow noopener" href="https://bulma.io/">Bluma</a>を使ってる。<br />
昔は、VuetifyやBootstrapを使っていたけど、ブレイクポイントが違っているのでまとめてみた(<em>´ω`</em>)</p>
<h2 id="ブレイクポイントのまとめ"><a href="#%E3%83%96%E3%83%AC%E3%82%A4%E3%82%AF%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81">ブレイクポイントのまとめ</a></h2>
<div class="table-responsive"><table>
<thead>
<tr>
<th></th>
<th>xs</th>
<th>sm</th>
<th>md</th>
<th>lg</th>
<th>xl</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bluma</td>
<td>< 769</td>
<td>< 1024</td>
<td>< 1216</td>
<td>< 1408</td>
<td>1408</td>
</tr>
<tr>
<td>Bootstrap</td>
<td>< 576</td>
<td>< 767</td>
<td>< 992</td>
<td>< 1200</td>
<td>1200</td>
</tr>
<tr>
<td>MaterialDesign</td>
<td>< 600</td>
<td>< 1024</td>
<td>< 1440</td>
<td>< 1920</td>
<td>1920</td>
</tr>
<tr>
<td>Vuetify</td>
<td>< 600</td>
<td>< 960</td>
<td>< 1264</td>
<td>< 1904</td>
<td>1904</td>
</tr>
<tr>
<td>Material-UI</td>
<td>< 600</td>
<td>< 960</td>
<td>< 1280</td>
<td>< 1920</td>
<td>1920</td>
</tr>
<tr>
<td>Element</td>
<td>< 768</td>
<td>< 992</td>
<td>< 1200</td>
<td>< 1920</td>
<td>1920</td>
</tr>
<tr>
<td>TwilwindCSS</td>
<td>< 640</td>
<td>< 768</td>
<td>< 1024</td>
<td>< 1280</td>
<td>1280</td>
</tr>
</tbody>
</table></div>
<p>だいたい5段階になってるっぽい。上限や下限がそれぞれ違うのがおもしろい(<em>´ω`</em>)<br />
ちなみに単位はpx。収まらなかったので省略...</p>
<h2 id="調べたのはこの7つ"><a href="#%E8%AA%BF%E3%81%B9%E3%81%9F%E3%81%AE%E3%81%AF%E3%81%93%E3%81%AE7%E3%81%A4">調べたのはこの7つ</a></h2>
<ol>
<li>Bluma ... <a target="_blank" rel="nofollow noopener" href="https://bulma.io/documentation/overview/responsiveness/">Responsiveness | Bulma</a></li>
<li>Bootstrap ... <a target="_blank" rel="nofollow noopener" href="https://getbootstrap.com/docs/4.3/layout/overview/#responsive-breakpoints">Overview · Bootstrap</a></li>
<li>Material Design ... <a target="_blank" rel="nofollow noopener" href="https://material.io/design/layout/responsive-layout-grid.html#breakpoints">Responsive layout grid - Material Design</a></li>
<li>Vuetify ... <a target="_blank" rel="nofollow noopener" href="https://vuetifyjs.com/ja/customization/breakpoints">Breakpoints — Vuetify.js</a></li>
<li>Material-UI ... <a target="_blank" rel="nofollow noopener" href="https://material-ui.com/customization/breakpoints/#breakpoints">Breakpoints - Material-UI</a></li>
<li>Element ... <a target="_blank" rel="nofollow noopener" href="https://element.eleme.io/#/en-US/component/layout#responsive-layout">Component | Element</a></li>
<li>Twilwind CSS ... <a target="_blank" rel="nofollow noopener" href="https://tailwindcss.com/docs/breakpoints/#app">Breakpoints - Tailwind CSS</a></li>
</ol>
<p>Material DesingはCSSフレームワークではないけど参考として。</p>
<h3 id="Bluma"><a href="#Bluma">Bluma</a></h3>
<p>参照元: <a target="_blank" rel="nofollow noopener" href="https://bulma.io/documentation/overview/responsiveness/">Responsiveness | Bulma: Free, open source, & modern CSS framework based on Flexbox</a></p>
<p>まずは使っているBluma。<br />
Bluma独特なのが、コードがxsなどではなく、デバイスの種類になっている点。<br />
classに書くときもわかりやすい。が、全体的に狭い範囲で細かい切り替えになっているよう。</p>
<div class="table-responsive"><table>
<thead>
<tr>
<th>コード</th>
<th>説明</th>
<th>範囲</th>
</tr>
</thead>
<tbody>
<tr>
<td>mobile</td>
<td>Mobile</td>
<td>< 769px</td>
</tr>
<tr>
<td>tablet</td>
<td>Tablet</td>
<td>< 1024px</td>
</tr>
<tr>
<td>desktop</td>
<td>Desktop</td>
<td>< 1216px</td>
</tr>
<tr>
<td>widescreen</td>
<td>Widescreen</td>
<td>< 1408px</td>
</tr>
<tr>
<td>fullhd</td>
<td>FullHD</td>
<td>1408px</td>
</tr>
</tbody>
</table></div>
<pre><code class="css">// Extra Small (phones)
// Small devices (tabletS)
@media (min-width: 770px) { ... }
// Medium devices (desktops)
@media (min-width: 1024px) { ... }
// Large devices (widescreen desktops)
@media (min-width: 1216px) { ... }
// Extra large devices (fullhd desktops)
@media (min-width: 1408px) { ... }
</code></pre>
<h3 id="Bootstrap"><a href="#Bootstrap">Bootstrap</a></h3>
<p>参照元: <a target="_blank" rel="nofollow noopener" href="https://getbootstrap.com/docs/4.3/layout/overview/#responsive-breakpoints">Overview · Bootstrap</a></p>
<p>xsのブレイクポイントが一番小さいのが印象的。</p>
<div class="table-responsive"><table>
<thead>
<tr>
<th>コード</th>
<th>説明</th>
<th>範囲</th>
</tr>
</thead>
<tbody>
<tr>
<td>xs</td>
<td>portrait phones</td>
<td>< 576px</td>
</tr>
<tr>
<td>sm</td>
<td>landscape phones</td>
<td>< 767px</td>
</tr>
<tr>
<td>md</td>
<td>tablets</td>
<td>< 992px</td>
</tr>
<tr>
<td>lg</td>
<td>desktops</td>
<td>< 1200px</td>
</tr>
<tr>
<td>xl</td>
<td>large desktops</td>
<td>1200px</td>
</tr>
</tbody>
</table></div>
<pre><code class="css">// Extra Small (portrait phones)
// Small devices (landscape phones)
@media (min-width: 576px) { ... }
// Medium devices (tablets)
@media (min-width: 768px) { ... }
// Large devices (desktops)
@media (min-width: 992px) { ... }
// Extra large devices (large desktops)
@media (min-width: 1200px) { ... }
</code></pre>
<h3 id="Material Design"><a href="#Material+Design">Material Design</a></h3>
<p>参照元: <a target="_blank" rel="nofollow noopener" href="https://material.io/design/layout/responsive-layout-grid.html#breakpoints">Responsive layout grid - Material Design</a></p>
<p>ホントの分類はこんな感じ。<br />
縦向き・横向きでそれぞれ決められているので、かなり細かい。</p>
<div class="table-responsive"><table>
<thead>
<tr>
<th>コード</th>
<th>説明</th>
<th>範囲</th>
</tr>
</thead>
<tbody>
<tr>
<td>xsmall</td>
<td>Portrait: small handset</td>
<td>< 360px</td>
</tr>
<tr>
<td>xsmall</td>
<td>Portrait: medium handset</td>
<td>< 400px</td>
</tr>
<tr>
<td>xsmall</td>
<td>Portrait: large handset</td>
<td>< 480px</td>
</tr>
<tr>
<td>xsmall</td>
<td>Portrait: large handsetLandscape: small handset</td>
<td>< 600px</td>
</tr>
<tr>
<td>small</td>
<td>Portrait: small tabletLandscape: medium handset</td>
<td>< 720px</td>
</tr>
<tr>
<td>small</td>
<td>Portrait: large tabletLandscape: large handset</td>
<td>< 840px</td>
</tr>
<tr>
<td>small</td>
<td>Portrait: large tabletLandscape: large handset</td>
<td>< 960px</td>
</tr>
<tr>
<td>small</td>
<td>Landscape: small tablet</td>
<td>< 1024px</td>
</tr>
<tr>
<td>medium</td>
<td>Landscape: large tablet</td>
<td>< 1280px</td>
</tr>
<tr>
<td>medium</td>
<td>Landscape: large tablet</td>
<td>< 1440px</td>
</tr>
<tr>
<td>large</td>
<td></td>
<td>< 1600px</td>
</tr>
<tr>
<td>large</td>
<td></td>
<td>< 1920px</td>
</tr>
<tr>
<td>xlarge</td>
<td></td>
<td>1920px</td>
</tr>
</tbody>
</table></div>
<pre><code class="css">// Extra Small (phones)
// Small devices (portrait tablets)
@media (min-width: 600px) { ... }
// Medium devices (landscape tablets)
@media (min-width: 1024px) { ... }
// Large devices (laptops)
@media (min-width: 1440px) { ... }
// Extra large devices (desktops)
@media (min-width: 1920px) { ... }
</code></pre>
<h3 id="Vuetify"><a href="#Vuetify">Vuetify</a></h3>
<p>参照元: <a target="_blank" rel="nofollow noopener" href="https://vuetifyjs.com/ja/customization/breakpoints">Breakpoints — Vuetify.js</a></p>
<p>Material Designを実現するためのCSSフレームワークなので、<br />
Material Designと区分けが近いが、少し異なるので注意が必要かも?</p>
<div class="table-responsive"><table>
<thead>
<tr>
<th>コード</th>
<th>説明</th>
<th>範囲</th>
</tr>
</thead>
<tbody>
<tr>
<td>xs</td>
<td>small to large handset</td>
<td>< 600px</td>
</tr>
<tr>
<td>sm</td>
<td>small to medium tablet</td>
<td>< 960px</td>
</tr>
<tr>
<td>md</td>
<td>large tablet to laptop</td>
<td>< 1264px</td>
</tr>
<tr>
<td>lg</td>
<td>desktop</td>
<td>< 1904px</td>
</tr>
<tr>
<td>xl</td>
<td>4k and ultra-wides</td>
<td>1904px</td>
</tr>
</tbody>
</table></div>
<pre><code class="css">// Extra Small (small to large handset)
// Small devices (small to medium tablet)
@media (min-width: 600px) { ... }
// Medium devices (large tablet to laptop)
@media (min-width: 960px) { ... }
// Large devices (desktop)
@media (min-width: 1264px) { ... }
// Extra large devices (4k and ultra-wides)
@media (min-width: 1904px) { ... }
</code></pre>
<h3 id="Material-UI"><a href="#Material-UI">Material-UI</a></h3>
<p>参照元: <a target="_blank" rel="nofollow noopener" href="https://material-ui.com/customization/breakpoints/#breakpoints">Breakpoints - Material-UI</a></p>
<p>こちらもMaterial Designを実現するためのCSSフレームワーク。<br />
ドキュメントには詳しい説明がないが、「Material DesignのSpecをsimplifiedしたぜ」とのこと。</p>
<div class="table-responsive"><table>
<thead>
<tr>
<th>コード</th>
<th>説明</th>
<th>範囲</th>
</tr>
</thead>
<tbody>
<tr>
<td>xs</td>
<td>extra-small</td>
<td>< 600px</td>
</tr>
<tr>
<td>sm</td>
<td>small</td>
<td>< 960px</td>
</tr>
<tr>
<td>md</td>
<td>medium</td>
<td>< 1280px</td>
</tr>
<tr>
<td>lg</td>
<td>large</td>
<td>< 1920px</td>
</tr>
<tr>
<td>xl</td>
<td>extra-large</td>
<td>1920px</td>
</tr>
</tbody>
</table></div>
<pre><code class="css">// Extra Small
// Small devices
@media (min-width: 600px) { ... }
// Medium devices
@media (min-width: 960px) { ... }
// Large devices
@media (min-width: 1280px) { ... }
// Extra large devices
@media (min-width: 1920px) { ... }
</code></pre>
<h3 id="Element UI"><a href="#Element+UI">Element UI</a></h3>
<p>参照元: <a target="_blank" rel="nofollow noopener" href="https://element.eleme.io/#/en-US/component/layout#responsive-layout">Component | Element</a></p>
<p>こちらもMaterial-UIと同様に、あまり説明はない。<br />
smやmdの値が少し独自な感じ。</p>
<div class="table-responsive"><table>
<thead>
<tr>
<th>コード</th>
<th>説明</th>
<th>範囲</th>
</tr>
</thead>
<tbody>
<tr>
<td>xs</td>
<td>extra small viewports</td>
<td>< 768px</td>
</tr>
<tr>
<td>sm</td>
<td>small viewports</td>
<td>< 992px</td>
</tr>
<tr>
<td>md</td>
<td>medium viewports</td>
<td>< 1200px</td>
</tr>
<tr>
<td>lg</td>
<td>large viewports</td>
<td>< 1920px</td>
</tr>
<tr>
<td>xl</td>
<td>extra large viewports</td>
<td>1920px</td>
</tr>
</tbody>
</table></div>
<pre><code class="css">// Extra Small
// Small devices
@media (min-width: 768px) { ... }
// Medium devices
@media (min-width: 992px) { ... }
// Large devices
@media (min-width: 1200px) { ... }
// Extra large devices
@media (min-width: 1920px) { ... }
</code></pre>
<h3 id="Tailwind CSS"><a href="#Tailwind+CSS">Tailwind CSS</a></h3>
<p>参照元: <a target="_blank" rel="nofollow noopener" href="https://tailwindcss.com/docs/breakpoints/#app">Breakpoints - Tailwind CSS</a></p>
<p>他と比べ、全体的に低めな値が印象的。</p>
<div class="table-responsive"><table>
<thead>
<tr>
<th>コード</th>
<th>説明</th>
<th>範囲</th>
</tr>
</thead>
<tbody>
<tr>
<td>xs</td>
<td>phones</td>
<td>< 640px</td>
</tr>
<tr>
<td>sm</td>
<td>tablet</td>
<td>< 768px</td>
</tr>
<tr>
<td>md</td>
<td></td>
<td>< 1024px</td>
</tr>
<tr>
<td>lg</td>
<td>laptop</td>
<td>< 1280px</td>
</tr>
<tr>
<td>xl</td>
<td>desktop</td>
<td>1280px</td>
</tr>
</tbody>
</table></div>
<pre><code class="css">// Extra Small
// Small devices
@media (min-width: 640px) { ... }
// Medium devices
@media (min-width: 768px) { ... }
// Large devices
@media (min-width: 1024px) { ... }
// Extra large devices
@media (min-width: 1280px) { ... }
</code></pre>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>いろんなフレームワークを横串で見ていくといろいろおもしろい。<br />
個別の対応をどこまで考えないといけないかなども見えてくるかも。<br />
実際にBlumaを使ってるけど、xs以下のサイズは、個別でブレイクポイントを設定したりなども。</p>
<p>新しいものを使うときはいろいろ見ないといけない(<em>´ω`</em>)</p>
<h2 id="こんなのつくってます。"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%E3%80%82">こんなのつくってます。</a></h2>
<p>最近、積読用の読書管理アプリ「積読ハウマッチ」をリリースしました!<br />
<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!<br />
CSSフレームワークにBlumaを使ってます(<em>´ω`</em>)</p>
<p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="25%"/></p>
<p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p>
<p>要望・感想・アドバイスなどあれば、<br />
公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで。</p>
きらぷか@積読ハウマッチ/SSSAPIなど
tag:crieit.net,2005:PublicArticle/14962
2019-05-01T23:44:03+09:00
2019-05-02T00:01:36+09:00
https://crieit.net/posts/Laravel
Laravelのページネーションをレスポンシブ対応する
<p>Laravelではクエリ実行時に<code>get</code>じゃなくて<code>paginate</code>メソッドを呼んでデータを取得することで、テンプレート側で<code>$posts->links()</code>とするだけでBootstrap4のページネーションのリンク一覧を表示することができる。</p>
<p>便利ではあるのだが、ページ数が増えると非常に横長になり、スマホ表示の場合は画面からはみ出しておかしくなってしまう。</p>
<p>この対処として、Laravel5.7から</p>
<pre><code class="php"><span>{</span><span>{</span> $users->onEachSide(5)->links() <span>}</span><span>}</span>
</code></pre>
<p>のようにすることで表示するページ番号数を調整することができるようになった。これは良いと思って試してみたのだが、最小の1にしてもたいして減らず、相変わらず画面からはみ出してしまう。このためだけに頑張って5.6から5.8に上げたのにもかかわらずだ。</p>
<h2 id="レスポンシブ対応"><a href="#%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B7%E3%83%96%E5%AF%BE%E5%BF%9C">レスポンシブ対応</a></h2>
<p>仕方がないのでレスポンシブ対応することにした。具体的には公式マニュアルに書いてあるように</p>
<pre><code>php artisan vendor:publish --tag=laravel-pagination
</code></pre>
<p>としてテンプレートをアプリケーション側に吐き出し、それを修正することで表示を調整することができるようになっている。なのでページ番号と「…」のところのspanとaタグのクラスに<code>d-none d-md-block</code>を追加することで、スマホ表示の場合は前のページと次のページへのリンクだけを表示するようにすることで対処した。</p>
<p>まあ、本来はCSS書いた方がスマートな気がするけど。</p>
だら@Crieit開発者