電通総研 テックブログ

電通総研が運営する技術ブログ

テックブログのデザイン刷新(実装編)

みなさん、こんにちは! 今回の記事は珍しく(?)以下のメンバーによる共同執筆となります。

  • 金融ソリューション事業部 市場系ソリューション 1 部 寺山
  • X イノベーション本部 AI トランスフォーメーションセンター 山田

さて、以前よりアクセスしていただいている方はお気づきかと期待していますが、10 月末に当ブログのデザイン(はてブロのテーマ)を刷新しました!
※PC 版のみ適用しています。

変更前

変更後

  • トップページ

  • 記事ページ

今回はデザインの実装における工夫等を紹介させていただきます!

デザイン刷新の目的

デザインを刷新した経緯/目的は以下のとおりです。

  • 記事や執筆者に一層フォーカスが当たるようにする
  • 当ブログの目的1に即し、ISID の魅力のアピールやテックブログから当社の別のチャネルへの導線を強化する
  • モダンでイケてるブログを投稿/運営することでモチベーションを上げる(?)

これらの目的やイメージを弊社の UX デザインセンターに伝え、議論した結果新しいデザインを提案してもらいました。
デザインの検討の観点では実際に担当した UX デザインセンターのメンバーが記事を投稿してくれる予定ですので、そちらをお待ちください!

デザイン実装の流れ

はてなブログのデザインをカスタマイズする方法はいくつかあるのですが、当ブログではテーマを自作した上で適用する方法を採用しました。

テーマを自作する方法はデザインテーマ制作の手引きとして公式よりヘルプが公開されています。

詳細は上記リンクで紹介されていますが、私たちがテーマを自作し適用するまでに行った流れは以下のとおりです。

  1. 動作確認用のサブブログの開設
  2. 動作確認用のエントリを用意

    公式のサンプルエントリーに加え、当ブログで公開済みの記事をコピーして動作(デザインの表示)確認に利用しました。

  3. サンプルテーマのBoilerplateリポジトリをクローンして当社の GitHub でホスト

  4. ゴリゴリ実装

    詳細は次のセクションで説明します。

  5. サブブログで動作確認

  6. テーマストアに投稿
  7. 当ブログに適用

工夫やこだわりポイント

環境構築

はてブロの手引きや Boilerplate にもあるとおり、テーマの実体は CSS です。管理者ページにてカスマイズできる HTMLを 含めて意図した修飾がされるように CSS を実装します。

環境構築は主に寺山が担当しました。インフラが主戦場な私は HTML/CSS に対しほぼ初心者ではあったのですが、環境構築において工夫した点は以下です。

  • 容易に素早く動作確認ができること
  • 知識がない者でも同じ環境を構築可能であること

具体的には以下のような環境としました。

一部のライブラリを変更

Boilerplate で使用しているLibSass(node-sass)deprecated であるため、後継であるDart-Sass(sass)に変更しました。

$ npm uninstall -D node-sass
$ npm i -D sass sass-migrator
$ npx sass --version
1.51.0 compiled with dart2js 2.16.2
$ npx sass-migrator --version
1.5.4 compiled with dart2js 2.15.1

LibSass から Dart-Sass への移行にはMigrator(sass-migrator)を利用しました。
sass-migrator は外部ライブラリのパスを探索できないため、node-sass 記法での import 文を以下のように修正しています。

// 修正前
@import "node_modules/normalize.css/normalize";

// 修正後
@import "normalize.css/normalize";

以下のようにツールを実行し、マイグレーションします。

$ npx sass-migrator module --migrate-deps --load-path node_modules scss/isid-modern-theme.scss

Boilerplate で定義済みの npm scripts も LibSass から Dart-Sass を実行するように修正します。

{
  /* 省略 */
  "scripts": {
    "prestart": "npm run build",
    "start": "npm-run-all -p watch server",
    // LibSassでの定義 "build": "npm-run-all scss autoprefixer",
    "build": "npm-run-all sass autoprefixer",
    // LibSassでの定義 "scss": "node-sass scss/isid-modern-theme.scss build/isid-modern-theme.css --output-style expanded --indent-width 4 --source-map build/",
    "sass": "sass --load-path=node_modules/ scss/isid-modern-theme.scss build/isid-modern-theme.css --source-map",
    "autoprefixer": "postcss --use autoprefixer -r build/isid-modern-theme.css",
    "server": "browser-sync start -c bs-config.js",
    "watch": "chokidar \"scss/\" -c \"npm run build\""
  },
  /* 省略 */
}

サブブログからコピーしたHTMLをホットリロード

手引きで紹介されているスタイルの確認方法は以下のとおりです。

こちらの方法では、はてなブログのデザインに反映されたことを確認するたび(≒Sass を実装するたび)に、ブログを手動でリロードする必要がありました。
また、デザインの実装は二人で担当していたので、スタイルやカスタム HTMLの変更が競合する懸念もありました。

そこで、当ブログのデザイン実装においては以下としました。

  1. 動作確認用のサブブログより HTMLをコピーし、リポジトリ内に HTMLファイルとして保存する
  2. VSCodeLiveServer 拡張機能を用いて HTMLをローカルサーバでホストする
  3. LiveServer にてホットリロードを実行させて、即座にローカルで確認可能とする

3. について、ブログよりコピーした HTMLを以下のように修正します。

<head>
    <!-- 省略 -->
    <link
        rel="stylesheet"
        type="text/css"
        href="https://cdn.blog.st-hatena.com/css/blog.css?version=7e80be95800d540eb0026a4db405b1"
    />

    <link rel="stylesheet" type="text/css" href="build/isid-modern-theme.css" /> <!-- ←ローカルでホストしているCSSを指定 -->
    <!-- 省略 -->
</head>

これにより、chokidar が Sass の変更を Watch して CSSコンパイルした際に、CSSWatch している LiveServer によってホットリロードが行われます。

VSCode の Remote Container を用いて同一環境を再現

各作業者の環境の同一性や手順化する範囲などは状況に依るでしょう。本件に限っては作業者も二人であるため、お互いサポートし合えば済んでしまった可能性は高いです。
しかし、今後のデザインの拡張やテックブログ運営の体制が変わっていく可能性や、そのときの担当者が寺山のように HTML/CSS の未経験/初心者の可能性もあります。また、VSCodeRemote Container(devcontainer)を用いれば比較的容易に同一環境を構築するフローは実現可能なので、本件では意識しました。

Remote Container の設定ファイルは以下のとおりです。

{
    /*---- コンテナボリューム ----*/ // ①
    "dockerComposeFile": "docker-compose.yml",
    "service": "devcontainer-isid-modern-theme",
    "workspaceFolder": "/workspaces",
    "postCreateCommand": "sudo chown -R node:node /workspaces && test -d <リポジトリ名> || git clone https://github.com/ISID/<リポジトリ名>.git ; cd <リポジトリ名>/ && chmod a+x .devcontainer/init.sh && .devcontainer/init.sh",
    /*---- 共通 ----*/
    "name": "HatenaTheme",
    "settings": {
        "liveServer.settings.CustomBrowser": "chrome",
        "eslint.validate": ["javascript", "typescript"],
        "stylelint.validate": ["css", "scss"],
        "editor.codeActionsOnSave": {
            "source.fixAll.stylelint": true
        },
        "css.validate": false,
        "scss.validate": false,
        "[scss]": {
            "editor.defaultFormatter": "stylelint.vscode-stylelint",
            "editor.formatOnSave": true,
            "editor.codeActionsOnSave": {
                "source.fixAll.stylelint": true
            }
        },
        "[css]": {
            "editor.defaultFormatter": "stylelint.vscode-stylelint",
            "editor.formatOnSave": true,
            "editor.codeActionsOnSave": {
                "source.fixAll.stylelint": true
            }
        },
        "[typescript]": {
            "editor.formatOnSave": true,
            "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[javascript]": {
            "editor.formatOnSave": true,
            "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[html]": {
            "editor.formatOnSave": true,
            "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[markdown]": {
            "editor.formatOnSave": true,
            "editor.defaultFormatter": "yzhang.markdown-all-in-one"
        }
    },
    "extensions": [
        "ms-ceintl.vscode-language-pack-ja",
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "stylelint.vscode-stylelint",
        "eamodio.gitlens",
        "ritwickdey.LiveServer",
        "ms-azuretools.vscode-docker",
        "yzhang.markdown-all-in-one",
        "bierner.github-markdown-preview"
    ],
    "remoteUser": "node"
}

Remote Container にした目的は、言語やランタイムに加え、VSCode拡張機能やその設定を同一にしつつ手順を簡略化するためです。
上記を実現する方法はさまざまな情報が他にあるため本記事では割愛します。

前のセクションで紹介したとおり、Sass の実装や動作確認では Watcher を利用しています。
弊社の社内標準となっているクライアント PC は Windows なのですが、Remote Container の標準の動作であるローカルホストのボリュームをコンテナにマウントする形式では Watcher が機能しないことが多いです。
確証はないのですが、これはホストとコンテナのファイルシステムが異なることにより、オーバーヘッドが大きくなっているためであると予想しています。

このことへの対応方法として Use Clone Repository in Container Volume でも紹介されている、コンテナボリュームにリポジトリをクローンする方式を実施してるのが、上記の設定ファイルの の部分です。

Remote Container へ接続した後にリポジトリのクローンを作業者が手動で行うのはコンテナ化して手順を簡略化していることにならないので、postCreateCommand を利用してコンテナのビルド時に自動で実行する様にしています。

"postCreateCommand": "sudo chown -R node:node /workspaces && test -d <リポジトリ名> || git clone https://github.com/ISID/<リポジトリ名>.git ; cd <リポジトリ名>/ && chmod a+x .devcontainer/init.sh && .devcontainer/init.sh"

init.sh の内容は以下です。

#!/bin/sh

## Install npm package
PKG_JSON="package.json"
if [ -f ${PKG_JSON} ]; then
  npm install
fi

SASS_MIGRATOR="node_modules/sass-migrator/sass-migrator.js"
if [ -f ${SASS_MIGRATOR} ]; then
  chmod a+x ${SASS_MIGRATOR}
fi

exit 0

このシェルスクリプトを用意している目的は NPM よりパッケージをインストールするためです。
本件に限らず、私は postCreateCommand よりスクリプトを実行する方法をよく採用します。処理内容は言語やパッケージマネージャごとに変えています。

執筆者を記事のタイトル下に表示

新しいデザインでは、刷新の目的にも記載した「執筆者にフォーカスが当たる様にする」目的で記事ページに執筆者のプロフィール画像と執筆者名(執筆者のアーカイブページへのリンク)を表示するようにしました。

これは JavaScript を用いて DOM を操作することで実現しています。

(function () {
    window.addEventListener('DOMContentLoaded', () => {
        const author = document.getElementsByClassName('author vcard')[0].querySelector('span.fn');
        const blogURL = document.getElementsByTagName('html')[0].getAttribute('data-blog-uri');
        const authorAnchor = document.createElement('a');
        authorAnchor.href = `${blogURL}archive/author/${author.innerHTML}`;
        authorAnchor.innerText = author.innerHTML;
        const p1 = document.createElement('p');
        p1.id = 'article-author-link';
        p1.appendChild(authorAnchor);

        const p2 = document.createElement('p');
        p2.id = 'article-author-image';
        const authorImage = document.createElement('img');
        authorImage.src = `https://cdn.profile-image.st-hatena.com/users/${author.innerHTML}/profile.png`;
        p2.appendChild(authorImage);

        const headerAuthor = document.createElement('div');
        headerAuthor.className = 'header-author';
        headerAuthor.appendChild(p2);
        headerAuthor.appendChild(p1);

        document.getElementsByClassName('entry-title')[0].after(headerAuthor);
    });
})();

はてなブログでは、カスタム HTMLを記述可能(HTMLを自由記述できる箇所を参照)なため、script タグでより柔軟に独自の要素を作成可能です。
上記の JavaScript は、デザイン設定 > カスタマイズ > 記事 > 記事上HTML(記事本文上) に記述しています。

自分のはてな ID を用いて色々試した結果、以下であることがわかりました。

  • プロフィール画像https://cdn.profile-image.st-hatena.com/users/<はてなID>/profile.png でホストされている
  • ブログメンバーごとのアーカイブページは https://tech.isid.co.jp/archive/author/<はてなID> として生成されている

したがって、 はてなID が分かればよいことになります。私は記事の下部にある author vcard クラスの span タグが、はてな ID に一致するものと判断しました。

後はスタイルをどのように記述するかを踏まえて DOM の操作を実装するだけです。
手引きに記載されている様に、HTMLの要素を配置することを禁止している範囲(デザインとコーディングの注意事項を参照)があるため、DOM を操作して要素を追加する場所には注意が必要です。

現在の当ブログの投稿フローでは、執筆者は全記事が ISID になってしまいます。ゆくゆくは実際に記事を作成したメンバーごとに執筆者名(はてな ID)の表示とアーカイブページを提供できるようにしたいと考えています。

トップページのカード型レイアウト

新しいデザインでは、トップページの記事一覧をカード型のコンポーネントで3列に表示するようにしました。

トップページの記事一覧表示自体は、はてなブログの設定から簡単にできるのですが、今回のように3列表示にする場合は追加でcssを当てる必要があります。 基本的な実装指針としては、はてなブログ側でレンダリングされるHTMLのDOMの構造には手を加えず、cssFlexboxを活用することにしています。

はてなブログ側でレンダリングされる記事一覧部分のHTMLは以下のような構造になっています。

<div class="archive-entries">
  <section class="archive-entry"></section>
  <section class="archive-entry"></section>
  <section class="archive-entry"></section></div>

大枠としてのdivタグには.archive-entriesクラスが適用されるので、このクラスにdisplay: flex;flex-direction: row;を当てることで各記事のカードはいい感じに並ぶようになっています。

あとは3列表示しつつ見栄えを整えるために以下の要素を考慮する必要があります。

  • カード間の余白
  • ひとつあたりのカードのサイズ

カード間の余白については、cssプロパティのgap (grid-gap)を利用しています。 gapはFlexboxやGridなどの行や列を使ったレイアウトをする際にその溝の大きさを定義できるプロパティです。

今回の実装では、.archive-entriesクラスにgap: 24pxとして設定をしています。 これによって、各記事のカードを配置する際にいい感じに余白を与えてくれます。

ひとつあたりのカードのサイズについては、cssプロパティのflex-basisを利用しています。 Flexboxでは内部に配置される各要素は、よしなに伸長したり縮小しますが、flex-basisを指定することで各要素のベースとなるサイズを与えることができます。 今回の実装では横幅と余白の関係性から.archive-entryflex-basis: 314px;として設定をしています。

記事カードの実装

次に、カード型にした記事情報の実装について紹介します。

こちらも基本的な実装指針としては同じく、はてなブログ側でレンダリングされるHTMLのDOMの構造には手を加えず、cssFlexboxを活用することにしています。

まず記事カードに含める情報を整理します。今回、記事カードに含める情報と表示したい要素の順は以下のとおりです。

  1. サムネイル画像
  2. 記事の公開日時
  3. 記事のタイトル
  4. 記事の概要
  5. 記事のカテゴリ

そして、はてなブログ側でレンダリングされる記事カード部分のHTMLは以下のような構造になっています。

<section class="archive-entry">
    <div class="archive-entry-header">
      <div class="date archive-date"></div>
           <h1 class="entry-title"></h1>
    </div>
    <div class="categories"></div>
    <a href="#" class="entry-thumb-link">
      <div class="entry-thumb" style="background-image: url('#');"></div>
    </a>
    <div class="archive-entry-body"></div>
</section>

こちらも大枠としてのdivタグに.archive-entryクラスが適応されるので、このクラスにdisplay: flex;とします。 そしてまず、考慮しなければならないのは表示する要素の順番です。 はてなブログ側でレンダリングされるHTMLをそのまま表示しようとすると要素は以下の順番になります。

  1. 記事の公開日時
  2. 記事のタイトル
  3. 記事のカテゴリ
  4. サムネイル画像
  5. 記事の概要

これは先程の表示したい要素の順番と異なります。 こういった場面で便利なのがFlexboxのorderプロパティです。

FLexbox内でorderプロパティを持つ要素は、その値が小さいものから順番に並んで表示されます。 今回は以下のようにorderプロパティを当てて順番を制御しています。

/* 記事のタイトルおよび記事の公開日時 */
.archive-entry-header {
    order: 1;
}

/* 記事の概要 */
.archive-entry-body {
    order: 2;
}

/* 記事のカテゴリ */
.categories {
    order: 3;
}

これによってはてなブログ側でレンダリングされるHTMLのDOM構造を変えることなく、もともと表示させたかった要素の順に見た目を整えています。 サムネイル画像のaタグの要素については、タブレットサイズでのレイアウトとの兼ね合いもあり、現時点ではorderプロパティで制御していません。

この実装の注意点としては、orderプロパティはアクセシビリティに影響を与える点があります。 音声読み上げソフトなどはもとの構造に従って、実行されるのためDOM構造そのものを変更できる場合は、きちんと意図の通りの順番にレンダリングするのが良いでしょう。

order プロパティとアクセシビリティhttps://developer.mozilla.org/ja/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items#order_%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%A8%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B7%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3

Lighthouse でのスコア測定

新デザインを適応前後に Lighthouse を使ってスコア測定をしました。

  • 新デザイン適応前のスコア(2022/10/11 時点)

  • 新デザイン適応後のスコア(2022/11/25 時点)

パフォーマンスのスコアは環境によっても変化するので、あくまで参考値ですが、新デザイン適応後はアクセシビリティやベストプラクティスについてはスコアが改善しました。 特にアクセシビリティの部分は配色やコントラストによって評価される項目のため、今回のデザイン刷新にあたりデザイナーの藤崎さんと協力して取り組めたことが大きかったと考えています。

やってみた感想

寺山
普段はインフラエンジニア/クラウドアーキテクトとしてアプリケーションをホストする基盤の開発を業務としているので、CSS とはいえ実際に目に見えるものを実装する作業は新鮮で楽しかったです。初心者であったため苦戦もしましたし、実際にかなり山田さんに助けてもらいましたが(汗)
React 等のフロントエンドフレームワークが主流になった現在において、 CSS 記法を業務で直接活用する機会は少ないと予想しています。一方、Markdown や SSG にちょっとデザインを加えたい場合などでは CSS が利用できるツールも多いので、今回身につけた知識を役立てていきたいと考えています!

山田
私も普段の業務では Python を書く機会が多く、CSSを書いてデザインを整える作業は学生時代のアルバイト以来でした。 はてなブログ側でレンダリングされるDOMの構造を保つことを意識しての、実装作業は思いの外大変で、はてなブログのデザインテーマを作成している方々は凄いなと素直に思いました。 今回の実装では、まだ実現できなかった部分や軽微な修正が必要な部分があるので日々改善を続けていきたいですね。

最後に

当ブログを通じて弊社の魅力がよりみなさんに伝わるよう、ブログのデザインも引き続き改善していく予定です。また、ソースコードリポジトリや、はてなブログのテーマをテーマストア上で公開するかもしれません。

記事の中身が一番の主役ではありますが、ブログのデザインにも時々着目していただければ幸いです!

執筆:寺山 輝 (@terayama.akira)Shodoで執筆されました


  1. ISID テックブログの狙いについては、ISIDがテックブログを始めた理由テックブログ始めました。をご覧ください。