サイトのCSS及びスクリプトを大幅に書き直しにめちゃくちゃ時間がかかった話

いつかやろうやろうと思ってたけど、なかなか進まなかった話なんですが、以前よりもCSSでできることが大幅に増えて、それでもChromeとFirefoxでは対応の違いがあって、:has()の問題が1つ、@container style()の問題がもう1つで、この記事を書いている2024/5/6現在では、Firefoxではまだ@container style()は対応していません。しかし@containerは対応していてまだ完全対応ではないと言う事があったりしていたのですが、それでも一部分を除き大抵はいけるのでまぁいいかと切り替えに乗り出しました。

ついでにネストで書き直しました。これがまたあっちに書いてあったりこっちに書いてあったりで大変でした。きっと値の上書き云々があって書いてたりしてたんでしょうが、同じことをクドクドと書いてあったので、それをまとめるのにも時間がかかるし新しい書き方に直すのにも色々とテストしたりもして時間だけかかる感じでした。

light-dark()が実装されて、ダークモードとライトモードが比較的簡単に切り替えられるようになったのも後押しした要因の一つです。

元々のテーマの状態

なんだかんだで4000行は無いものの、3000行はあると言う感じのcssでした。だいぶ昔に書かれたものらしく、

selector {
    transform: rotate(45deg);
    -ms-transform: rotate(45deg);
    -webkit-transform: rotate(45deg);
}

このような感じに、もうIEはないんやで、というようなものがたくさんあって、これをまとめて削除するだけでもだいぶ減量できそうな感じでした。敵はpost-cssとかで自動でベンダープレフィックスをつけてるんでしょうが、それらを外すのがまぁ色々と大変で。

それを外すと正しく表示されるのか?というような微妙なものもあったりして、いちいちcodepenですべて試してからやってたわけですが、やはり新しくなるのであれば色々と試したくなる部分もあってそれらを実験してるのにだいぶ時間がかかりました。

その中でも、position: fixed;問題は未だに解決できない謎の一つです。親要素にtransformfilterなどが使われてるとうまく動作しない云々があるのですが、そんなもの使ってねぇよと。 しかし、同じcssなのに前の状態では正しく動作して、新しくしたものは固定されないなど、どこに原因があるのかの特定が今でもできずじまい。新しくしたものに問題があるのでしょうが多分気にもしていないような微妙な所が原因になっているのでしょう、全くそれがわからずでマジメンドクサイ…となりましたので、javascriptで位置を変えてやりました…としようとまでは思いましたがそれも面倒くさいので、jQueryでやりました。

例えば、PCの画面(960px以上)でなら表示される.scrolled-meta。SNSの共有リンクとかその他がSVGのアイコンで並べられている、画面左側で固定されてる部分ですが、これは.main-wrapperの中に入っていました。

構造を大体で書きますが、もう頭おかしいんじゃないの?ぐらい入れ子になっています。

body
    .header-wrapper
    .main-wrapper
        .main-top-wrapper
        .inner-wrapper
            .blog-wrapper
                .main-section
                    .widget.blog
                        .blog-posts.container
                            .blog-post
                                .scrolled-meta  /* SNS共有 */
                                .item-post-inner
                                    .post-header
                                    .post-body  /* ブログ記事 */
                                    #comments
                                    .post-footer
    .footer-wrapper

もうナニコレと。bloggerは、sectionの中にwidgetが入るので、そこらはわからんでもないですが、自分が作ってるテーマだと、

body
    .header-area
    .main.container
        .blog_main_wrapper section
        .widget.blog
            .blog-posts
                .post-outer-container
                    .post-content  /* ブログ記事 */
                    .post-footer
                    .comments
    .sidebar
    .footer

こうなってます。これでも深いなぁと思ってたんですがブログがwidgetの中に入ってるんでしょうがないと諦めつつ、レイアウト的にはほぼ同じ機能があります。bloggerの機能では足りないのでjavascriptをふんだんに使って機能追加している部分はありますが、このブログでは機能を補うために、また正しく動作させるために無駄にjavascriptで魔改造しないと動作しなかったわけです。

結果的に、

body
    .header-wrapper
    .main-wrapper
        .main-top-wrapper
        .inner-wrapper
            .blog-wrapper
                .main-section
                    .widget.blog
                        .blog-posts.container
                            .blog-post
                                .item-post-inner
                                    .post-header
                                    .post-body  /* ブログ記事 */
                                    .post-footer
    #comments
    .scrolled-meta  /* SNS共有 */
    .footer-wrapper

こうしました。.main-wrapperに何かしらの原因があるのは確定的だと思います。と言うのも、position: fixed;させたい要素を.main-wrapperの外に配置したら、普通にそのままのcssで動作しましたので。しかし原因っぽいものはなく、以前のcssでは正常に動作していたので更に原因がわからずで。

移動させるだけなら大したことではないですがDOMContentLoadedで、動作するようにしてあるものを新たに違う箇所に後から移動した場合正しく動作しない場合があるので、同じ処理を移動した後に動作するようにしないといけません。

ページの読み込み時に設定して、ページの読み込み完了後に動作させる仕組み

例えば、コメントのリンク(.item .comments)はhtmlに既に書かれているために、ページに訪問した際には既に見えています。しかし、

$(()=>{
    let comments = $("#comments"); 
     $(".footer-wrapper").before(comments);
});

このような感じで書かれていると、大体はhtmlの閉じbodyの付近にスクリプトは配置されているため、一瞬チラついたような感じの後で位置が変更されてそれから動作するような感じになります。

もしかするとスクリプトの状態によってはすぐに動作しないかもしれませんし、何かしら問題が出ることもあるような場合のものもあるかと思います。

他の例で言えば、事前に準備してあってスクロールとか何かしらのきっかで動作するlazyloadとかその手の類のものとか。
そういうDOMContentLoadedで準備して、ページの読み込みが完了したloadの時に実行するようなタイプのものは次のようにして書くことができます。

const commentLoad = (function(){
    const comments = document.querySelectorAll(".item .post-comment", ".item .post-comments");

    // before load
    function beforeLoad() {
        comments.forEach((a)=> {
            a.classList.add("preload");
        });
    }

    //after load
    function afterLoad() {
        comments.forEach((a)=> {
            a.classList.remove("preload");
        });
    }

    // event listener
    document.addEventListener('DOMContentLoaded', beforeLoad);
    window.addEventListener('load', afterLoad);

    return {
        beforeLoad,
        afterLoad
    };
})();

commentLoad.beforeLoad();
commentLoad.afterLoad();

ページ読み込み時にする処理をbeforeLoadに書きます。ここでは、document.querySelectorAllで指定したセレクタを順番に.preloadと言うクラスをつけていきます。

.preloadでは、

.preload {
    pointer-events: none;
    opacity: 0.5;
    cursor: wait; /*option*/
}

などを書いておけば、対象の要素は、半透明でクリックイベントなどができないようになります。

そして、ページ読み込み完了後にするのはafterLoad書きます。ここでは読み込み時につけたクラスを外していきます。そうすると、元の状態に戻るので、半透明は解除されてクリックができるようになります。

当サイトではYoutubeの動画などはサムネで取得してクリックタップしたものだけがその場に埋め込まれるようにしてあるので他のまとめブログなどよりは比較的早くにページの読み込みが完了します。
そのためこれら処理は不要といえばそうですが、テザリングなどを用いて野外でこういった記事を見ている時、ページのloadを待ってから実行するのはかなり待たされるサイトがあります。
まとめサイトなどではimgurなどの埋込をたくさんしているサイトなどがありますが、広告のスクリプトや他のスクリプトの読み込みも含め、画像そのものがダイレクトに読み込まれるので回線が細いと表示するまでに何分も待つ事になったりしますから、その後実行するのでは遅すぎて何も操作できない場合があります。
そういう場合はボトルネックになっているスクリプトを後で読み込ましたり、画像は後で読み込むなどの遅延させるアイデアが必要になります。

通常なら、このような(前述のような)書き方をせずに、

const SELECTOR = document.querySelector("対象セレクター");

document.addEventListener('DOMContentLoaded', () =>{
    //読み込み時の処理
});


window.addEventListener('load', () => {
    //完了後の処理
});

のように書けば良いだけのことなんですが、この処理をする前に対象セレクターでconstを使用していた場合、すでに定義されてる云々でエラーになります。

同じセレクターを対象にする場合はスコープ内に入れて区切ればよいので、即時関数とか色々と方法がありますが、上↑↑で書いたような関数に関数を入れて、スコープの外で実行するモジュール形式(こう呼ぶのが正しいかは不明)にするのが後から見た時にもわかりやすいのではないかと。

他にも、クラス形式で書く方法もあります。慣れてる人はクラス形式のほうが良いのでしょうが個人的にはわかりにくいので関数のスコープの中にまとめて実行するのがまぁ良いのではないかと。

自分で最初から書いているならもう少し他の書き方もあるのかもしれませんが他人が書いたスクリプトをなるべく壊さずかつ、新たに書くというのは色々面倒くさいものです。

CSSのレイアウトについて

現在のレイアウトはトップページでは、横に12列gridで区切って、左から1~7番目までがメイン、8~12番目までがサイドバーにしてあって、他の部分は1~12番目までが1つのカラムになるような仕組みになっています。

これが投稿ページでは、1カラムになっています。これらは、bodyタグに.index.itemと言うクラスが付けば、その子要素の.inner-wrapperなどが変更されるようになっています。

こう言う時に、便利なのが:has()で、実はFirefoxではこれが実装されるまでだいぶ時間があったのでそれまでサイトのcssの変更は待とうと思っていた次第でした。 chromeではだいぶ前に実装されているので少なくても2023年の夏頃までにはそう思っていたはずです。

特に、セレクタ:not(:has(.xx))の書き方ができなくて色々面倒くさい状態だったのですが、更に時間が過ぎて、@container@layerが実装され:has()を使わなくても同じようなことができるやんと思った次第ですが、更に新たな問題として@container (width < [nn]px)などは実装されているのに、@container style(変数: true)がFirefoxでまだ実装されていないと言う状況で、「おい、Mozilla。実装しろよバカ野郎」と今現在も思っています。これはどういうものかと言いますと、例えばhtmlの:rootで変数などを登録しておいて、

html {
    :root {
        --isMobile: false;
        @media (width < 480px) {
            --isMobile: true;
        }
    }
}

このようにすると、画面サイズが480px以下の時に--isMobile: false -> trueに変わると言うようなプログラムチックな書き方ができるわけです。そこで使用するのが、@containerで、

.test {
    img {
        margin: 1rem     
    } 
}

このようなものをデフォルトとして書いておいて、

@container style(--isMobile: true) {
    .test {
        img {
            margin: 1rem 0;
        }
    }
}

このようなものを後から追記しておくと画面サイズが480px以下と言うのがtrueの時だけ画像のmargin: 1rem -> 1rem 0と左右にもあったmarginを0にできるわけです。

更に@containerは、予めcontainer: sidebar / inline-sizeを任意の要素に忍ばせておくだけで、例えば、

.sidebar {
    container: sidebar / inline-size;
    width: ...

    :is(ul,ol) {
        ...
    }
}

このようにサイドバーに仕込んでおいて、

@container sidebar (400px <= width) {
    :is(ul,ol) {
        display: flex;
        list-style: none;
    }
}

という感じに、サイドバーのサイズが400px以上になったらリストを横並びにするような事ができるわけです。

つまり、この中で、@container style()が実装されていないのでそれを使っている当サイトではFirefoxで色々と問題が出ていますが、まぁいいかと。概ね動作しています。

長々と書いてますが、まとめると

Firefoxではまだ完全に実装されていないものをサイトで使ってますが、Chrome系ではほぼ問題ないと思いますのでそのまま行きますって事です。

Firefoxでは、レスポンシブで画面を狭めた時、あるいはスマホで見た時に要素の左右マージンが無かったりするぐらいかと思います。

あと、使い方は限られることも多いですが、ページ読み込み時に準備しておいて、ページ読み込み完了時に実行するようなものの考え方としてはこう言う感じかなぁというのも書きました。 htmlが自分でイジれる場合は、事前準備は直でhtmlに、

<div class="item">
    <div class="post-comment preload">コメントを開く</div>
</div>

等として書いておく方が早いです。で、他の何かしらのスクリプトの実行後.preloadを外せば(外す関数を実行すれば)ページ読み込みと同時に近い段階で同じことができます。 htmlをいじれない、あるいはいじるのが面倒くさい場合は、今回書いたような方法を使うしかないのかなぁと。

更に、あとbloggerの編集画面では単にテキストに色を付けるだけの事をしてありますが、ダークモードだと青色(#00f)の文字とかは見えにくいので、モードを判別しているわけではないですが、本文中の色のついているテキストは文字色を背景色にするようにしました。 このあたりは何か良い方法が思いついたら変更するかもしれません。