tag:crieit.net,2005:https://crieit.net/tags/jQuery/feed 「jQuery」の記事 - Crieit Crieitでタグ「jQuery」に投稿された最近の記事 2022-04-30T00:03:10+09:00 https://crieit.net/tags/jQuery/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/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/16363 2020-12-14T21:10:33+09:00 2020-12-14T21:10:33+09:00 https://crieit.net/posts/mouse-stalker-sample-20201214 カーソルを特定要素にホバーすると画像を表示するマウスストーカーのサンプル <p>マウスストーカーが盛んに取り上げられるようになって早数年。さすがにそろそろやり方くらいは……と思い、試してみることにしました。</p> <h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2> <p>まずは成果物を。</p> <ul> <li>要素ホバーで拡大: <a target="_blank" rel="nofollow noopener" href="http://scrunchy56.starfree.jp/vampire_stoker_first/">Home - Vampire Stoker</a></li> <li>要素ホバーで背景画像表示: <a target="_blank" rel="nofollow noopener" href="http://scrunchy56.starfree.jp/vampire_stoker_last/">Home - Vampire Stoker</a></li> </ul> <p>最初の方(要素ホバーで拡大)は参考コードほぼそのままです。次のものはマウスストーカーが大きくなるだけでなく、背景画像をスポットライト的に切り抜いて表示する機能も付けたものになります。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/arm-band/test_vampire_stoker">arm-band/test_vampire_stoker</a></li> </ul> <p>リポジトリ。</p> <h3 id="HTML"><a href="#HTML">HTML</a></h3> <pre><code class="html"><div class="c-mouseStalker_wrapper"> <div class="c-mouseStalker"> <div class="c-mouseStalker_cursor" id="c-mouseStalker_cursor"></div> <div class="c-mouseStalker_delay" id="c-mouseStalker_delay"></div> </div> </div> </code></pre> <p>HTMLはいたってシンプル。カーソル用の要素とディレイがかかって追いかけてくる要素の2つの <code>div</code> を用意します。</p> <p>今回はさらにラッパーで覆って <code>overflow: hidden;</code> をかけることで、画面端にカーソルが移動した際にディレイ要素の大きさ分だけはみ出てスクロールバーが表示されないようにしました。</p> <pre><code class="html"><script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js" defer></script> </code></pre> <p>それから、今回はサクッと試すために CDN で TweenMax を読み込むようにしました。</p> <h3 id="css(Scss)"><a href="#css%28Scss%29">css(Scss)</a></h3> <pre><code class="scss">body { position: relative; // 元々のマウスカーソルを消す cursor: none; } .c-mouseStalker { // イベント反応させなくする pointer-events: none; width: 100%; height: 100%; position: absolute; top: 0; left: 0; &_wrapper { // イベント反応させなくする pointer-events: none; width: 100%; height: 100%; position: absolute; top: 0; left: 0; // 端にカーソルを持って行ったときにディレイの部分だけはみ出てスクロールバーが表示されてしまうのを抑止 overflow: hidden; } &_cursor, &_delay { position: absolute; top: 0; left: 0; pointer-events: none; // イベント反応させなくする } &_cursor { width: 0.5rem; height: 0.5rem; background-color: f.$color; z-index: 10001; border-radius: 50%; } &_delay { $delayRadius: 100px / 4; width: 100%; height: 100%; background-color: f.$main-color; z-index: 10000; transition: clip-path ease 0.1s; // 遅くするともっさりした感じになる opacity: 0.4; // at 以降は円の中心の定義 clip-path: ellipse($delayRadius $delayRadius at 50% 50%); &.active { $activeTimes: 4; background: { image: url("../img/img.jpg"); attachment: fixed; size: cover; position: center center; } opacity: 1; clip-path: ellipse($delayRadius * $activeTimes $delayRadius * $activeTimes at 50% 50%); } } } </code></pre> <ul> <li><code>body</code> の <code>cursor: none;</code>: デフォルトのマウスカーソルを非表示に</li> <li><code>.c-mouseStalker</code> と <code>.c-mouseStalker_wrapper</code>: <ul> <li><code>pointer-events: none;</code></li> <li>どちらも表示画面サイズいっぱいまで拡大</li> </ul></li> <li><code>.c-mouseStalker_wrapper</code>: <code>overflow: hidden;</code> で端にカーソルを持って行ったときにディレイの部分だけはみ出てスクロールバーが表示されてしまうのを抑止</li> <li><code>.c-mouseStalker_delay</code>: 肝 <ul> <li>要素自体は透明。 <code>clip-path</code> で切り取った円形の範囲のみ見える状態にする <ul> <li>デフォルトは単色半透明のマスク</li> <li>特定要素にホバーしたとき、 jQuery でクラス付与 (ホバー解除時にクラスも削除) <ul> <li>クラスが付与されたとき、背景を単色から <code>background-image</code> で指定した画像にする <ul> <li>背景画像は <code>background-attachment</code>, <code>background-size</code>, <code>background-position</code> 指定あり</li> </ul></li> </ul></li> </ul></li> <li><code>transition: clip-path ease 0.1s;</code> でホバー時の拡大縮小等の切り替えを easing <ul> <li><code>0.3s</code> にすると TweenMax のディレイも相まってもっさりした動きになってしまうので、 <code>0.1s</code> で</li> </ul></li> </ul></li> </ul> <h3 id="JavaScript (jQuery + TweenMax)"><a href="#JavaScript+%28jQuery+%2B+TweenMax%29">JavaScript (jQuery + TweenMax)</a></h3> <pre><code class="javascript">// mouse stalker const mouseStalker = () => { const $cursor = $('#c-mouseStalker_cursor'); const $delay = $('#c-mouseStalker_delay'); const paramsArray = { cursor: { width: $cursor.outerWidth(), coorX: 0, coorY: 0, delay: 0.001, }, delay: { width: $delay.outerWidth(), coorX: 0, coorY: 0, delay: 6, }, }; const activeClass = 'active'; let clipRadius = 100 / 4; let clipScale = 1; let clipPathCoor = `ellipse(${clipRadius * clipScale}px ${clipRadius * clipScale}px at 50% 50%)`; // カーソルの遅延アニメーション // ほんの少しだけ遅延させる (0.001秒) TweenMax.to( {}, paramsArray.cursor.delay, { repeat: -1, onRepeat: function() { paramsArray.delay.coorX += (paramsArray.cursor.coorX - paramsArray.delay.coorX) / paramsArray.delay.delay; paramsArray.delay.coorY += (paramsArray.cursor.coorY - paramsArray.delay.coorY) / paramsArray.delay.delay; clipPathCoor = `ellipse(${clipRadius * clipScale}px ${clipRadius * clipScale}px at ${paramsArray.delay.coorX}px ${paramsArray.delay.coorY}px)`; // delay TweenMax.set( $delay, { css: { clipPath: clipPathCoor, } } ); // cursor TweenMax.set( $cursor, { css: { left: paramsArray.cursor.coorX - (paramsArray.cursor.width / 2), top: paramsArray.cursor.coorY - (paramsArray.cursor.width / 2), } } ); } } ); // mouse hover $('#hoverElmID').on({ mouseenter: function () { $cursor.addClass(activeClass); $delay.addClass(activeClass); clipScale = 4; }, mouseleave: function () { $cursor.removeClass(activeClass); $delay.removeClass(activeClass); clipScale = 1; }, }); // get mouse coordinate $(document).on('mousemove', function (e) { paramsArray.cursor.coorX = e.pageX; paramsArray.cursor.coorY = e.pageY; }); }; window.addEventListener('load', () => { mouseStalker(); }); </code></pre> <ul> <li>だいたいサンプルコードに則ったコード</li> <li>カーソルとディレイ要素の各種パラメータは別の変数で持っていると個人的に分かりづらかったのでオブジェクトにまとめました</li> <li>変数 <code>clipPathCoor</code> で指定した値が実際の <code>clip-path</code> で切り取られるディレイ要素の指定 <ul> <li>カーソル用はほぼ弄らず</li> <li>ディレイ要素の方は TweenMax の <code>to</code> で指定するパラメータが <code>top</code>, <code>left</code> から <code>clip-path</code> のみに変更</li> </ul></li> <li>特定要素のホバー時に上述 Scss の指定が反映されるようにクラスの付け外しを実施 <ul> <li>同時に <code>clip-path</code> で切り取られる範囲を拡大縮小(拡大率の数値を変更している)</li> </ul></li> </ul> <hr /> <p>今回はこれで望んだ挙動になりました。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <h3 id="マウスストーカー"><a href="#%E3%83%9E%E3%82%A6%E3%82%B9%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AB%E3%83%BC">マウスストーカー</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.evoworx.co.jp/blog/mouse-stoker-gsap/">イケてるマウスカーソルを簡単に実装する | 株式会社 エヴォワークス -EVOWORX-</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://un-tech.jp/tweenmax-started/">Googleも推奨!アニメーションライブラリTweenMaxの使い方 入門編 | un-Tech</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://greensock.com/licensing/">Licensing - GreenSock</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://greensock.com/standard-license/">Standard License - GreenSock</a></li> </ul> <h3 id="背景画像切り抜き"><a href="#%E8%83%8C%E6%99%AF%E7%94%BB%E5%83%8F%E5%88%87%E3%82%8A%E6%8A%9C%E3%81%8D">背景画像切り抜き</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://teratail.com/questions/246261">JavaScript - マウスストーカーを透明のマスクにして背景を動的に切り取りたいのですが、どうすれば良いでしょうか?|teratail</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/CSS/basic-shape"> - CSS: カスケーディングスタイルシート | MDN</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://www.htmq.com/css/clip-path.shtml">clip-path-CSSリファレンス</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/duka/items/e65354f103ebc9280959">CSS animation で遊び倒す - CSS Clip Path- - Qiita</a></li> </ul> <h3 id="要素内座標(未使用)"><a href="#%E8%A6%81%E7%B4%A0%E5%86%85%E5%BA%A7%E6%A8%99%28%E6%9C%AA%E4%BD%BF%E7%94%A8%29">要素内座標(未使用)</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://muumv.com/grid/">要素内のマウスカーソルの座標を取得する!スマホ対応【Javascript】 | MUUMV</a></li> </ul> arm-band tag:crieit.net,2005:PublicArticle/15477 2019-10-13T23:43:10+09:00 2019-10-13T23:43:10+09:00 https://crieit.net/posts/960b0aeb50068c42582d201c7dd14467 熨斗(のし)の王様を作ったときに気をつけたこと <h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://noshi-king.matsubarase.com">のし王</a>というWebサービスを作った。<br /> <a href="https://crieit.now.sh/upload_images/fffb317da90f57ce80e2986ead5be3bd5da337e03f206.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fffb317da90f57ce80e2986ead5be3bd5da337e03f206.png?mw=700" alt="のし王サムネイル" /></a></li> <li>のし作成サービスが既に存在する中で、下記に気をつけたというお話。 <ul> <li>サービス作成のきっかけになった不満点・ニーズをもれなく仕様に落とし込む</li> <li>仕様策定時は技術的な楽さに甘えず、ユーザー体験を優先する</li> <li>上記を実現するため必要最低限の技術を選定する</li> </ul></li> </ul> <h1 id="不満点の解消、ニーズを仕様に落とし込む"><a href="#%E4%B8%8D%E6%BA%80%E7%82%B9%E3%81%AE%E8%A7%A3%E6%B6%88%E3%80%81%E3%83%8B%E3%83%BC%E3%82%BA%E3%82%92%E4%BB%95%E6%A7%98%E3%81%AB%E8%90%BD%E3%81%A8%E3%81%97%E8%BE%BC%E3%82%80">不満点の解消、ニーズを仕様に落とし込む</a></h1> <ul> <li>ちょっとした贈り物に「のし」をつけたかった。</li> <li>既存サービスは、以下の点で不満が残った。 <ul> <li>「のし紙」のテンプレート(背景画像)だけが提供されており、Wordなどに貼り付けた後、自分で文字を描かないとダメだった</li> <li>Web上で任意の文字まで入れられるサービスでは、最後になるまで仕上がり具合がが分からなかった</li> <li>最終画像にサービス名のロゴが入ってしまい、贈り物には不向きだった</li> <li>フォントがしょぼかった</li> <li>水引き(背景画像)や表書き(御礼などの上部の文字)をどう選んでよいか分からなかった</li> </ul></li> <li>不満点の解消をもれなく仕様に落とし込んだ。 <ul> <li>背景画像と任意の文字を組み合わせてPDFで出力できるようにする</li> <li>最終出力結果には広告を入れない</li> <li>有料でもカッコいいフォントを採用する</li> <li>用途に応じて自動で「水引き」と「表書き」が選択できるようにする</li> </ul></li> <li>マッスルの神様 = マ神 → 熨斗の王様 = のし王</li> </ul> <h1 id="ユーザー体験を最優先する"><a href="#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E4%BD%93%E9%A8%93%E3%82%92%E6%9C%80%E5%84%AA%E5%85%88%E3%81%99%E3%82%8B">ユーザー体験を最優先する</a></h1> <div class="table-responsive"><table> <thead> <tr> <th align="left"></th> <th align="left">技術的な楽さを優先</th> <th align="left">ユーザー体験を優先</th> </tr> </thead> <tbody> <tr> <td align="left">文字描画</td> <td align="left">ユーザー自身がWordなどで描画</td> <td align="left">Webサービス上で描画</td> </tr> <tr> <td align="left">仕上がり確認</td> <td align="left">プレビュー不可 or プレビューボタンクリックで表示</td> <td align="left">リアルタイムプレビュー表示</td> </tr> <tr> <td align="left">水引き、表書き</td> <td align="left">ユーザーの選択したものを表示</td> <td align="left">用途を選ぶと最適な水引き、表書きを自動で選択</td> </tr> <tr> <td align="left">描画フォント</td> <td align="left">無料の範囲で選択可能</td> <td align="left">有料で質の良いフォントが選択可能</td> </tr> <tr> <td align="left">ユーザーが泥酔状態</td> <td align="left">酔いが冷めてから使う</td> <td align="left">泥酔状態でも直感で使える</td> </tr> </tbody> </table></div> <h1 id="仕様の実現方法を考える"><a href="#%E4%BB%95%E6%A7%98%E3%81%AE%E5%AE%9F%E7%8F%BE%E6%96%B9%E6%B3%95%E3%82%92%E8%80%83%E3%81%88%E3%82%8B">仕様の実現方法を考える</a></h1> <ul> <li>使用した技術 <ul> <li>Webサービス上で文字描画しPDF出力 <ul> <li>水引きの種類や表書きなどの情報をブラウザ上で選択させる</li> <li>サーバーでPDFを作成してダウンロードさせる(Golang/Google App Engine)</li> </ul></li> <li>リアルタイムプレビュー表示 <ul> <li>サムネイル描画サーバー(Golang/Google App Engine)を準備</li> <li>水引きの種類や表書きなどの情報が変更されたらAjax(jQuery)でサーバーからサムネイル画像を取得し、ブラウザ上に即時反映</li> </ul></li> <li>用途を選ぶと最適な水引き、表書きを自動で選択 <ul> <li><a target="_blank" rel="nofollow noopener" href="https://wikiki.github.io/components/quickview/">Bulma-ExtensionsのQuickView</a>で用途一覧を表示</li> <li>用途を入力させて絞り込み(jQuery)</li> <li>用途クリックで「水引き」と「表書き」を最適なものに変更(jQuery)</li> </ul></li> </ul></li> <li>選定理由 <ul> <li>仕様実現に際して最低限/シンプルなものを選ぶ</li> <li>最新の技術よりも枯れて安定した技術を選ぶ</li> <li>なるべくメンテナンスが不要なものを選ぶ</li> </ul></li> </ul> Matsubarase.com公式 tag:crieit.net,2005:PublicArticle/15223 2019-07-11T00:15:45+09:00 2019-07-11T00:15:45+09:00 https://crieit.net/posts/Webpack4-jQuery Webpack4 ビルドするフロントエンド環境で jQuery に依存したライブラリを使う <p>バックエンドにPython3+bottle, フロントエンドにJavascript+jQueryを使って個人Webサービスを開発しています。<br /> (いまだに jQuery なん? というツッコミは甘んじて総受けいたします…)</p> <p>GUI を作り込んでいくにあたり、型安全の安心に包まれて開発したいので Typescript への移行を目論んでいます。<br /> ついでに Javascript のモジュールの仕組もモダンな ES Modules を導入したかったので、ビルドツールとして <a target="_blank" rel="nofollow noopener" href="https://webpack.js.org/">Webpack4</a> を導入しました。</p> <p>現在は <code><script></code> タグで多数の Javascript を読込み、グローバルな変数や関数が散在しているのを整理しながら、ES Modules 方式に直している所です。<br /> jQuery を使用している箇所では、</p> <pre><code class="javascript">import $ from 'jquery'; </code></pre> <p>のように先頭に書いておけば、<code>$("#code")</code> のような書式はそのまま ES Module 環境でも動作します。</p> <p>ところが、テキスト編集に <a target="_blank" rel="nofollow noopener" href="https://github.com/NicolasCARPi/jquery_jeditable">jQuery-jeditable</a> プラグインを使用していた箇所の修正でつまづいてしまいました。<br /> このプラグイン自体は npm にあるのでインストール自体はすんなりできましたが、</p> <pre><code class="bash">> npm install --save-dev jquery-jeditable </code></pre> <p>実行中に jQuery が参照できない、というエラーが出てしいましました。ライブラリの中なので、↑の import 文を追記することもできません。(直接 node_modules ディレクトリ以下のソースを書き換える事はできますが、npm 管理に任せておきたいのでやりたくはありません)</p> <p>結論としては、Qiita 記事「<a target="_blank" rel="nofollow noopener" href="https://qiita.com/soarflat/items/28bf799f7e0335b68186">webpack 4 入門</a>」Webpack4 公式サイト内の「<a target="_blank" rel="nofollow noopener" href="https://webpack.js.org/plugins/provide-plugin/">ProvidePlugin | webpack</a>」を参考に、以下のように <code>webpack.config.js</code> を修正することで解消しました。</p> <pre><code class="javascript">module.exports = { : plugins: [ new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }) ], : }; </code></pre> <p>これにより、Webpack4 でビルドする全ての Javascript ファイルに対して、<code>$</code>, <code>jQuery</code> 双方の変数が使えるように効にできるため、node_modules 以下にある jQuery-jeditable からも参照できるようです。</p> <p>…こんなんで悩むくらいなら「ヘイYou、Vue.js とか使っちゃいなYo!」ですか? そうですね、そう遠くない将来は私もそうしたいと考えています。(もくもく会でもいろんな方からこうしたアドバイスを頂きます…)<br /> しかし、jQuery から Vue.js への移行というのは個人的に移行しづらい障壁があると思っています。</p> <p>それは、移行する技術が2つあることです。1つはフレームワーク(ライブラリ)自身、もう1つはビルド環境です。<br /> HTML から <code><script></code> タグで Javascript を読み込むだけだった牧歌的なあの頃から、Webpack4 を使ったモダンなビルド環境へ移行するのは自分にとって簡単ではありませんでした。(ビルドツールだけでも色々あるので、どれに移行すべきかも当初は見えていませんでした)</p> <p>こうした状況では、開発経験が豊富なあなたならきっと内なる心のささやきが聞こえるはず。</p> <pre><code class="text">「複数の技術を同時に移行しようとすると、路頭に迷う」 </code></pre> <p>未知の用語や設定を見かけた時に、どちらの技術に由来するのかが不明なため、ドキュメントを読む手間が倍増します。<br /> 何かトラブルが起きた時に、どちらの技術に関わるものか、切り分けが困難になります。</p> <p>多少なりともコード資産がある状況では、段階を踏んだ移行により心理的な安全を確保しながら進むのが大事だと思っています。</p> ともたこ tag:crieit.net,2005:PublicArticle/14787 2019-02-08T11:58:47+09:00 2019-02-08T11:58:47+09:00 https://crieit.net/posts/jquery-enter-submit jqueryでフォームパーツ上でenterが押されたときにsubmitする <p>例えばフォーム処理を全部buttonのonclickなどでやってしまって、form内にsubmitボタンがないときなどの対処法。</p> <p>ひとまずフォームパーツにフォーカスが当たってるときにenterが押されたら無差別にsubmitします。</p> <pre><code>$('input').keypress(function(e){ if(e.which == 13) { $(this).parents('form').submit(); return false; } }); </code></pre> <p>P.S.<br /> コードの色分けとかってどうしたら良いんだろうか? 記事の書き方をまとめた記事などもいいかも</p> yuki2021 tag:crieit.net,2005:PublicArticle/14601 2018-11-14T03:32:24+09:00 2018-11-14T04:10:54+09:00 https://crieit.net/posts/Web-100 Web未経験から100日でリリース!「お笑いライブの検索サイト」をつくりました【個人開発】 <p>こんにちは、かしい(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/rubys8arks">@rubys8arks</a>)と申します。<br /> SIerで働いていましたが、WEB業界への転職を目指しプログラミングの勉強を始めました。<br /> データベースなどの知識は少しあったものの、Webアプリケーションの開発は今回が完全に初めてです!</p> <h1 id="作ったもの"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE">作ったもの</a></h1> <p>お笑いライブの検索サイトです。日付やキーワードで東京近郊のお笑いライブを横断検索できます。<br /> - <a target="_blank" rel="nofollow noopener" href="https://warally.info">ワラリー! - お笑いライブ検索(東京)</a></p> <blockquote class="twitter-tweet" data-lang="fr"><p lang="ja" dir="ltr">🎊Web系未経験の初心者が100日間で初めてのrailsサービスをリリース🎊👉<a target="_blank" rel="nofollow noopener" href="https://t.co/qL6fAtOi5i">https://t.co/qL6fAtOi5i</a>首都圏のお笑いライブを日付やキーワードで横断検索できるサービスです!・5秒で使えるTwitterログイン・わかりやすいUI・13サイトからデータを毎日スクレイピングよかったら見てください〜✨ <a target="_blank" rel="nofollow noopener" href="https://t.co/7js7F9Bs8p">pic.twitter.com/7js7F9Bs8p</a></p>— かしい@お笑いSNSつくった (@rubys8arks) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/rubys8arks/status/1061748089751515137?ref_src=twsrc%5Etfw">11 novembre 2018</a></blockquote> <p><img width="337" alt="スクリーンショット 2018-11-13 18.58.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/238801/068ccb8d-ab67-c558-9e04-b279ddb801ce.png"><img width="337" alt="スクリーンショット 2018-11-13 19.01.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/238801/e9b5e0c0-51d7-29ed-ff64-c8305d9fd755.png"></p> <p>データはプレイガイドや劇場のサイトから自動でスクレイピングするほか、誰でもライブ情報を登録できるようになっています。</p> <p><img src="https://qiita-image-store.s3.amazonaws.com/0/238801/13926ae2-5133-570c-86ca-0901e79b055f.jpeg" alt="ワラリー!ビジネスモデル図.jpg" /><br /> * <a target="_blank" rel="nofollow noopener" href="https://note.mu/tck/n/n95812964bcbb?magazine_key=m91891e5390bc">ビジモ図鑑</a>で配布されているキットをお借りしました。</p> <h1 id="サービスを作ったきっかけ"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%8D%E3%81%A3%E3%81%8B%E3%81%91">サービスを作ったきっかけ</a></h1> <p>もともと「Walive!」というお笑いライブの検索サイトがあったのですが、そのサイトが2017年9月で突然閉鎖してしまったことがきっかけです。<br /> せっかくお笑いファンの間で愛されているサイトだったのにもったいないなと思い、同じ機能(+α)を持った新しいサイトを作ろうと考えました。<br /> <img width="600" alt="スクリーンショット 2018-11-13 19.23.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/238801/756b7047-435e-a639-0421-e4f4a4395b07.png"></p> <h1 id="どういう技術で動いてるか"><a href="#%E3%81%A9%E3%81%86%E3%81%84%E3%81%86%E6%8A%80%E8%A1%93%E3%81%A7%E5%8B%95%E3%81%84%E3%81%A6%E3%82%8B%E3%81%8B">どういう技術で動いてるか</a></h1> <p>初心者なのであまり凝った技術は使っていません!</p> <h2 id="バックエンド"><a href="#%E3%83%90%E3%83%83%E3%82%AF%E3%82%A8%E3%83%B3%E3%83%89">バックエンド</a></h2> <h3 id="Ruby on Rails"><a href="#Ruby+on+Rails">Ruby on Rails</a></h3> <p>webスクレイピング用に<a target="_blank" rel="nofollow noopener" href="https://github.com/sparklemotion/mechanize">Mechanize</a>のgemを使ってます。サービス層は悪!と言われてることもあるのですが、コントローラーやモデルが見やすくなるのでサービス層、ヘルパー層も使いました。</p> <h2 id="サーバー"><a href="#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC">サーバー</a></h2> <h3 id="Heroku"><a href="#Heroku">Heroku</a></h3> <p>手軽にデプロイできるHerokuのサーバーを使っています。<br /> アクセスできない時間ができるのが嫌だったので、Herokuの有料プランにしています。<br /> - Hobby Dyno $7/月 (システムダウンタイムなし、独自ドメインのかんたん設定)<br /> - Heroku Postgres Premium $9/月(1000万行まで保存できる)</p> <p>Herokuの有料プランは使わずにもっと安くする方法もあるみたいなんですが、一旦は設定のしやすさを優先しています。</p> <h2 id="フロントエンド"><a href="#%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89">フロントエンド</a></h2> <h3 id="JQuery"><a href="#JQuery">JQuery</a></h3> <p>さんざん「JQueryはオワコン」と言われていますが、ぐぐったときに出てくる情報が多いので一旦JQueryで実装しました。<br /> 日付の選択をカレンダ(<a target="_blank" rel="nofollow noopener" href="https://flatpickr.js.org/">flatpickr</a>)で行えるようにしたり、アンケート結果をアコーディオンでぬるっと表示させたりしています。<br /> <img width="339" alt="スクリーンショット 2018-11-13 19.32.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/238801/a15b6af6-0ca5-398f-3a51-22a54bf96210.png"></p> <h3 id="fontawesome"><a href="#fontawesome">fontawesome</a></h3> <p>みんな大好き<a target="_blank" rel="nofollow noopener" href="https://fontawesome.com/">fontawesome</a>です。<br /> フォームのplaceholderにも使っています。</p> <h1 id="こだわったこと"><a href="#%E3%81%93%E3%81%A0%E3%82%8F%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">こだわったこと</a></h1> <h2 id="UIとデザイン"><a href="#UI%E3%81%A8%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3">UIとデザイン</a></h2> <p>「とにかく人に使ってほしい」という思いからUIとデザイン(特にライブの入力フォーム回り)にこだわりました。<br /> 実際にサービスへの反応も「デザインがいい!」というものが多く、ありがたい限りです。<br /> CSSのフレームワークは使わず、TumblrのEstenテーマ、Twitter、InstagramなどのSNSを参考にしました。<br /> <img width="312" alt="スクリーンショット 2018-11-13 20.32.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/238801/a45d9e30-287d-a752-ee65-e7f4fbe33380.png"><img width="336" alt="スクリーンショット 2018-11-13 20.32.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/238801/3385c57f-2855-7b5e-5b60-a95cf021cc56.png"></p> <ul> <li>URLを貼り付けたらogp情報を取ってライブの説明や出演者、画像などを自動入力できるように</li> <li>会場をセレクトボックスで選択できるようにしつつ、自由入力欄も作ったり</li> <li>フォーム数が多くなると入力したくなくなるので、重要でない項目はモーダルで分けたり</li> </ul> <p>自分にとってはだいぶ使いやすくなったのですが、フォームはまだまだ改善の余地があるなと思っています。</p> <h2 id="SNSログイン"><a href="#SNS%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3">SNSログイン</a></h2> <p>お笑いファンはだいたいTwitterで情報収集しているので、既にアカウントを持っているであろうTwitterアカウントで登録・ログインできるようにしました。<br /> 知らないサービスにいきなり「メールアドレスで登録できます!」と言われてもユーザー登録しないなと思ったので、SNS連携としました。</p> <h2 id="いいね機能"><a href="#%E3%81%84%E3%81%84%E3%81%AD%E6%A9%9F%E8%83%BD">いいね機能</a></h2> <p>旧サイト「Walive!」にはなかった機能として、ライブへのいいね機能を2種類実装しました。<br /> ライブに行く人あるあるとして、自分のスケジュールを把握できずに同じ日の同じ時間帯のライブ(いわゆる『被り』)のチケットを間違えて買っちゃったりします。<br /> そういうのを防止したいなと思い、すでにチケットを買った・予約したライブは『絶対いく』、まだ買ってないけど行けたら行きたいライブは『気になる』で管理できるようにしました。</p> <h1 id="Webサービスをつくってみた感想"><a href="#Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F%E6%84%9F%E6%83%B3">Webサービスをつくってみた感想</a></h1> <p>最初の1ヶ月はプログラミングスクールでhtml/cssとruby/Ruby on Railsを学んだのですが、スタッフの「1ヶ月ですぐ作れるよ!」という甘い声をうのみにして痛い目にあいました…。<br /> スクールを終えた知識で作ってみても「え、いいねボタン自動で切り替わらないの?(JQueryが必要)」、「孫モデルまでアソシエーションしてデータ取るのどうやるの?」などなどわからないことだらけ。<br /> 最初の頃、家で同じエラーに1週間くらいわんわん言ってた私に「macbookを買え、もくもく会に行け」と言いたいです。<br /> Twitter、Slack、もくもく会などでド初心者の質問に親切に答えてくださった皆さん、本当にありがとうございました!!</p> <p>エンジニアの方にはあまりお笑いライブに馴染みはないかもしれませんが、せっかく作ったのでちら見してもらえれば幸いです。<br /> 出演者ですぐ探せるので、「こないだテレビで見た芸人面白かったな」というときはぜひ使ってみてください!</p> <p><a target="_blank" rel="nofollow noopener" href="https://warally.info">ワラリー! - お笑いライブ検索(東京)</a></p> かしい@お笑いSNS作成中