スタイルを隔離してHTMLを埋め込むにはShadowDOMを使うとスマートかもしれない

管理画面があるようなWebサイト・Webアプリを作るとき、稀によくHTMLテキストを値として入力したいという要件があります。

Webサイトの運用者がある程度自由に内容を書き換えられる必要があるコンテンツ、例えばWordPressの記事本文などがそうですが、管理画面ではWYSIWYGエディタのようなものだったり、場合によってはmarkdownを書く仕様だったりすると思います。

これを表示する側の実装ではその部分にも当然スタイルを当てなければなりませんが、多くの場合は出力されるHTMLに自由にクラス名などを付与することができないため、 <h1><p> など、クラス名のついていない素のタグに対してスタイルを適用することになります。

例えば、下記のような。

<div class="post_content">
  <h1>見出し1</h1>
  <p>ほげほげ</p>
  <h2>見出し2</h2>
  <ul>
    <li>リスト1</li>
    <li>リスト2</li>
  </ul>
</div>
.post_content h1 {
  font-size: 24px;
  font-weight: bold;
}
.post_content h2 {
  font-size: 20px;
  font-weight: bold;
}

.post_content p {
  margin-top: 12px;
  margin-bottom: 12px;
}

.post_content ul {
  list-style: disc;
}

...

私はこういうスタイルを書くとき、このサイトがベースのスタイルとしてtailwindやリセットCSSを使っているとしたら、わりと気を使うんですよね。

h1やpのようなよく使うタグはもちろんデザインに合わせてスタイルを書いておきますが、 <blockquote> みたいな普段使わないような、デザインの想定にもないようなタグを使われた場合、スタイルを用意できておらず見た目ではdivと同じになってしまいます。

normalize.cssをベースにしていれば少なくともデフォルトのスタイルが存在するので、スタイルがないよりマシだと思いますが、ベースがリセットCSSだと前後の余白すらなくなってしまって酷い見た目になります。

上記の例で言うところの .post_content 以下だけCSS的に隔離ができて、normalize.cssもしくは独自のCSSを適用できるのだとしたら、外からの影響を受けずに堅牢なスタイルが作れるのではないかと思いました。

親要素からの隔離だったら ShadowDOM が使えるのでは? というのが今回の話の趣旨になります。

ShadowDOMとは?

詳しい説明はみんなだいすき MdN を見てもらうとして、一言でいうならばDOMツリーの中に独立したDOMツリーを構築する機能のこと。

その影響としてCSSのスコーピングが可能になります。

ShadowDOMの外はtailwind、中はnormalizeができるわけですね。

<div class="js-dom-prison">
  <h1>見出し1</h1>
  <p>ほげほげ</p>
  <h2>見出し2</h2>
  <ul>
    <li>リスト1</li>
    <li>リスト2</li>
  </ul>
</div>

.js-dom-prison をShadowDOMのrootにして、内容物をすべてShadowDOMの中に配置し直します。

const container = document.querySelector('.js-dom-prison');
const children = container.childNodes;
const shadowRoot = container.attachShadow({mode: 'open'});
shadowRoot.append(...children);

ShadowDOMの中にnormalize.css(もちろん、独自のCSSでも可)を適用したい場合は、こう↓

const container = document.querySelector('.js-dom-prison');
const children = container.childNodes;
const shadowRoot = container.attachShadow({mode: 'open'});
const stylesheet = document.createElement('link');
stylesheet.setAttribute('href', 'https://necolas.github.io/normalize.css/8.0.1/normalize.css');
stylesheet.setAttribute('rel', 'stylesheet');
shadowRoot.append(stylesheet, ...children);

これをDevToolsでみるとこうなります↓

あえてinnerHTMLなどを使わずに最初から .js-dom-prison にHTMLを展開しておくことで、たぶんSEO的にも問題にならない。(たぶん)

querySelector等で探せなくなるなど、デメリットがないわけではないので使い所が選ぶ必要がありそうですが、iframeなどと違ってオーバーヘッドも小さいですし、もののJS数行でCSS的に隔離ができるので、ハマる状況ならとてもスマートなんじゃいかと思ってます

Alpine.jsの$storeはいつ使うのか?

Alpine.jsでは x-data="{open: false}" というように x-data 属性を付与することで、その要素の内側で x-show="open" のように参照することができます。

<div x-data="{open: false}">
  <button @click="open = !open">開閉するよ</button>
  <div x-show="open">
    開いてるね
  </div>
</div>

とまあこれはAlpine.jsを知っている人なら全員知ってることなんですが、 x-data でなくても $store で同じようなことができます。

例えばこんな感じですね

<button x-data @click="$store.menu.open = !$store.menu.open">開閉するよ</button>

<div x-data x-show="$store.menu.open">
  開いてるね
</div>

<script>
  document.addEventListener('alpine:init', () => {
    Alpine.store('menu', {
      open: false,
    })
  })
</script>

コードでみると結構違いますが、やってることは一緒で、openという変数の定義場所が違うだけなんですよね。

$store はコンポーネントを跨いで参照することができます。便利。

ただし、v3から x-data入れ子になっていても変数名がカブらない限り祖先の変数も参照できるという仕様なので、

<body x-data="{open: false}">
  ...
  <button @click="open = !open">開閉するよ</button>
  ...
  <div x-show="open">
    開いてるね
  </div>
  ...
</body>

このように <body>x-data しちゃえばどこからでも参照できるグローバル変数を定義できちゃうんですよね。これでは$storeの立場がありません。

じゃあ $store っていつ使うの?って話なんですが、おそらく主に下記2点に注目すべきポイントがあります

外部のJSと連携したい!

UI系のライブラリを利用する場合など、Alpine.js外のJSとステートの共有をしたい場合は往々にしてあると思います。

そんな場合には x-dataAlpine.data で頑張るより素直にstoreを使ったほうが良いでしょう。

<script>
    Alpine.store('menu').open = true;
</script>

パフォーマンスへの影響

x-data を付与すると Alpine.start() を実行したときに、その要素以下がコンポーネントとして初期化されます。

もしその要素が巨大なツリーだった場合、初期化にかかるコストが大きいものになるため、できる限りコンポーネントは小さくするのがベター。

ちょっとした状態管理のためだけにbodyで x-data をしてしまうのはコストに見合わないので、$storeを使うのがよいでしょう。

Figmaの「マルチ編集」はズボラデザイナーの救世主となるか

Figmaのアップデートで「マルチ編集」機能が追加されました。

編集したいオブジェクト(またはテキストなど)を選択したあとに
Command+Option+Aを押すとフレーム間をまたいで一括で選択されるという、
今までありそうでなかった代物です。

フレームをまたげると、たくさんのフレームを置いてるデザインファイルの作業時には特に便利そうです。

↓公式のFigmaファイルはこちら
Multi-edit playground

まずは選択したいオブジェクトを選ぶ

Command+Option+Aを押すと、
フレームをまたいで同じオブジェクトが選択される
その後は任意で色変えたり色々できる

オブジェクトを選択してShiftを押すと
どこにあるかが青色のボックスで表示される
確認するだけならこれでもいいかも

これコンポーネントでよくね?

しかし、この機能を知ったFigmaユーザーはこう思うでしょう。

「これ編集するなら最初からコンポーネントにてしおけばよくね?」

…私も、最初はそう思いました。
コンポーネント化しとけば編集も楽じゃん? と。

しかし使い所がちょっと違うのです、こいつは…

コンポーネント化する前に役立つんだ

このマルチ機能、私のようなズボラデザイナーの活用方法としては
このような場面で役立つと思っています

このアイコンこのページでしか使わないから
コンポーネント化しなくていっか!

なんだかんだ複数ページに使用することになるが、
そのまま複製して使う

やっぱコンポーネントにしとけばよかったと後悔しつつ
置き換え作業をする

こういった雑なデザインファイルを作った時に役立つのです。

一括で選択できるから、同じオブジェクトがどこに置いてあるかかすぐにわかるのです
ズボラデザイナー(主に私)にとっては、救世主のような存在になるかもしれません

後々困るのは自分だとはめちゃくちゃ分かっているんですが、
勢いでガーッと作ってる時はコンポーネント化めんどい!後でやろう!と、
ついその場のライブ感を優先してしまうこと、ありますよ…ね?

ファイルやコンポーネント管理をきっちりしている方も、
マルチ機能に救われる場面があるとおもいます。

あなたもFigmaを信じましょう。Figma is GOD.

Google Map APIの高度なマーカーのクリックイベントに関するリファレンスが間違ってる

下記はリファレンスのイベントから抜粋したもの。

イベント 説明
click (前略) addEventListener() では使用できません(代わりに gmp-click を使用してください)。
gmp-click (前略) (addListener() ではなく)addEventListener() で使用することをおすすめします。

これを見ると addEventListener('gmp-click') とするのが正しそうだが、これは動かない。
ドキュメントを見ると addListener('click')addListener('gmp-click') で良さそう。(欲しい引数の型でどちらか好きな方を選ぶ)

Docker for Macを捨てcolimaでやっていく

Docer for Macの問題点としてはとにかくI/Oが遅い点です。
nuxt build などフロントエンドのビルドをDockerマシーン上で行うと非常に遅く、プロジェクトの規模とマシンスペックによっては30分以上かかるケースがあります。

また、Docker for Macが有料化されたこともあり、Docker for Macを避ける会社も出てきているかと思います。

Colimaとは

ColimaLimaをラップしたコンテナランタイムで、最小の設定でLimaを通してdockerなどのコンテナを利用することができます。

そもそもLimaはWindowsにおけるWSL2のように、Linuxの仮想環境をMac上で実行するものです。Limaを使ってdockerを利用することもできますが、多くの設定をする必要があり煩雑でした。

Colimaを使うことで、仮想Linux環境上でDockerを利用することができ、多くの場合Docker for Macを利用するよりも高速に、特にI/Oについては大幅な改善が見られます。

Colimaのインストール方法については公式ドキュメントに任せますが、特殊な環境ということもありいくつかのtipsをまとめてみました。

Dockerコンテナ内からDNSが引けずにnpm installやcomposer installなどが失敗する

colimaで作成したdocker環境ではホストのDNSが引けず、DNS解決に失敗することがあります。たとえば npm install や composer install をした場合に「リポジトリが見つからないのでインストールに失敗した」というようなことがあります。

この場合は、下記のようにColimaの設定で明示的にDNSのIPを指定することで解決します。ここではパブリックDNSを指定する必要があります。(Google Public DNSの 8.8.8.8 など)

colima start --dns 8.8.8.8

Rosetta 2を利用する

ARM対応していないDockerイメージを利用することも多々あるかと思います。

ARM MacかつMacOS 13 (Ventura)以降であれば下記のオプションでRosetta 2が利用できます。(Rosetta 2の対応自体はLimaにて行われいます)

colima start --arch aarch64 --vm-type=vz --vz-rosetta

Docker環境がうんともすんとも言わなくなる

nuxt devなどを利用中にHMRが実行されなくなったり、レスポンスが全く返ってこなくなったり、することがあります。この場合、ColimaかLimaがハングしている場合があります。

columa restart をしDockerコンテナを立ち上げ直すことで解決します。

最後に

Colimaを利用することでnuxt buildの時間が半分以下になるケースもありました。よいDXの為には高速な開発環境が必要です。是非Colimaを利用してみてください。

いい感じの色を作りたいならOklchを使うといい感じになる

時折、明度と彩度を一定に保ったまま色相を変えていくつかの色を作りたいときがあります。

なすびのウェブサイトで例えると、制作事例のタグは頻繁な増減があり、数も多く、色自体に拘りたいわけではないので、すべての色をひとつずつ決めるのは結構な手間になります。
そのため、ここでは codePointAt() を使ってタグ名を数値に変換し、色相に当てはめてランダムではない動的な色を作ることにしました。

このような場合、これまではhsl()を使うことで簡単に明度彩度を固定したまま色相だけ違う色をたくさん作ることができましたが、hslによる指定では明度彩度を一定にしても視覚的な均一性はありませんでした(特に黄色系の色味が明るすぎるなど)。

最近のブラウザではhslに似た指定で視覚的均一性のあるカラー指定oklch()が使えるようになっています。

hslとoklchをわかりやすく並べて比較するとこのくらい違いがありました。

上下のどちらの色が均一に見えるでしょうか。

色相だけでなく、例えばデザインシステムのカラー設計で色相と明度(+彩度)を軸にしたカラーグリッドを作る場合でも、Oklchを使うことで視覚的に統一された色を簡単に作ることができます。

使用する上で注意点を挙げるとするならば、oklchはカラースペースがsRGBではなく、より広い範囲の色を指定することができることです。

極彩色に近い色だとディスプレイによっては色の差がわからないことがあるかもしれません。

とはいえ一般的なスマートフォンやMacbookのようなdisplay-p3対応を謳っているディスプレイであれば、ほぼ正確に出力できます。


参考にさせていただいたサイト
https://griponminds.jp/blog/relearn-css-color/

FrankenPHPでサクッとWordPressの開発環境を作る

FrankenPHPは先日v1.0がリリースされたばかりの、PHPとWebサーバがセットになったDockerイメージです。

ちょうどローカルでWordPressを動かしたいタイミングがあったので試してみました

基本的にはdunglas/frankenphp-wordpressにひな形があるのでこれを参考にします。

まずはDockerfile。
ほぼひな形通りですが、PHP8.3以上の環境だとImagickのビルドでコケるので、今回は明示的に sha-2eabec8-php8.2 を指定しています。

FROM dunglas/frankenphp:sha-2eabec8-php8.2

# install the PHP extensions we need (https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions)
RUN install-php-extensions \
    bcmath \
    exif \
    gd \
    intl \
    mysqli \
    zip \
    imagick \
    opcache

COPY --from=wordpress /usr/local/etc/php/conf.d/* /usr/local/etc/php/conf.d/
COPY --from=wordpress /usr/local/bin/docker-entrypoint.sh /usr/local/bin/
COPY --from=wordpress --chown=root:root /usr/src/wordpress /app/public

VOLUME /app/public

RUN sed -i \
    -e 's/\[ "$1" = '\''php-fpm'\'' \]/\[\[ "$1" == frankenphp* \]\]/g' \
    -e 's/php-fpm/frankenphp/g' \
    -e 's#/usr/src/wordpress#/app/public#g' \
    /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]

docker-compose.ymlはそのままでも大丈夫そうだけど、好みの問題で下記のようにしました

version: '3.1'

services:

  wordpress:
    build: .
    ports:
      - 80:80
      - 443:443
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: docker
      WORDPRESS_DB_PASSWORD: docker
      WORDPRESS_DB_NAME: dbname
    volumes:
      - ./:/app/public

  db:
    image: mysql
    environment:
      MYSQL_DATABASE: dbname
      MYSQL_USER: docker
      MYSQL_PASSWORD: docker
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    ports:
      - 3306:3306

上記2ファイルをドキュメントルートの階層に置いて、起動すればlocalhostでアクセスできました。

docker compose up

ウェブサイトでSVGを配置して、cssでドロップシャドウを付ける話

以前の投稿では、SVGの色をcssを使って変更する方法についてお話ししました。今回は、SVGに影(ドロップシャドウ)を追加する方法に焦点を当てたいと思います。

divにドロップシャドウを付ける場合は、以下のようにcssのfilterプロパティでdrop-shadow関数を使用することができます。

.divShape {
  display: block;
  width: 500px;
  height: 500px;
  background: #222;
  filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4)); 
}

前回のカスタムシェイプの場合、この方法ではドロップシャドウが適用できません。

この場合、擬似要素を使用します。::before擬似要素をカスタムシェイプに適用し、その親であるdivにドロップシャドウを追加します。

このようにすることで、カスタムシェイプのdivにドロップシャドウを効果的に付けることができます。

.svgShape {
  position: relative;
  width: 606px;
  height: 514px;
  filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4)); 
}

.svgShape::before {
  display: block;
  height: 100%;
  content: "";
  background: linear-gradient(45deg, black, transparent);
  mask-size: contain;
  mask-image: url('/img/sample.svg');   
}

ウェブサイトでSVGを配置して、cssで色を変更する話

SVGをウェブページに追加する最も簡単な方法は、<svg>タグをインラインで直接記述するか、<img>要素を追加してsrc属性でSVG画像を参照することです。
SVGファイル直接に使用すると、SVGの色を変えたい時は少しややこしいです。(編集ソフトを使用して、書き直す必要があります。)
(<img> 要素を使用してインポートした場合、CSSだけで色を変更することはできません。)

単色を変えたい場合:
css実装例:

#svgID path{   
  fill: red; 
}

このようにすればcssでpath要素の色を簡単に変更できるだと思いますが、グラデーションをかけたい場合はSVGファイル自体を修正しなければなりません。そこでもう一つの方法を紹介します。

DIV要素をカスタムシェイプに:

SVGインラインで直接記述するより、DIV要素の形をSVGにすれば、色々カスタマイズができます。今回は外部SVGを参照して、cssで色をグラデーションにします。

divといえば、多くの人のイメージには長方形や正方形しかありません。しかしdivを他の形にすることができます。この時はmask-imageを使います。
(単純にdivの形を変えたい場合はclip-pathも出来ますが、今回の主旨と違いますので詳細は割愛させていただきます)

HTML実装例:
<div class=”svgShape”></div>
css実装例:
(widthとheightはsvgのサイズに合わせてください)

.svgShape {
  width: 500px; 
  height: 500px;
  background: linear-gradient(45deg, black, transparent);
  mask-image: url('/img/sample.svg');
}


このように実装すれば、divはsvgの形に変えられ、色はcssでカスタマイズできます。
実装例:

refrence:

https://developer.mozilla.org/ja/docs/Web/CSS/mask-image

Figmaでアイコン等をSlotで管理してみる

Figmaでデザイン制作する際に、
1つのアイコンを様々な大きさで使う場面があるとします。

これをVariantsでまとめておくとこんなかんじになります。

3サイズを8つの絵柄で作ると合計24個。ちょっと多い。

これをもっとスマートにまとめる
「FigmaのSlotでコンポーネントを管理する方法」があるらしく、
これを使うとコンポーネント置き場がすっきり綺麗になるそうです。

↑こんな感じ。8つの絵柄だけでOK

なので試しにやってみたぞ、というお話です。

Slotってなに?

そもそもSlotって? Figmaの機能かな?と思ってたのですが、どうやらそうではない様子。空のコンポーネントを作り、その中に入るインスタンス要素を入れ替えているようです。

参考にした動画とFigmaファイルです↓

https://www.figma.com/community/file/969234311094210750

(この動画ではカードレイアウトで解説されています)

やってみよう

1. Slotコンポーネントをつくる

まずインスタンスを入れる空のSlotコンポーネントをつくります。

長方形ツールで縦横16pxの図形を製作し、Slotという名前を付け
右クリック→「コンポーネントの作成」からコンポーネント化します。

コンポーネント化したら、もう一度選択して右クリック→「オートレイアウトの追加」を選択。右側のオートレイアウトプロパティより上下の間隔やパディングを全て0にします。

※わかりやすいようにピンクの点線で囲んでいます。

2. Slotの複数サイズ展開をつくる

デザイン内でアイコンを使用する場面を想定して、様々なサイズ展開を作ります。
今回はS(16px)M(32px)L(64px)の3種類にします。

先程製作したSlotをアセットから複製しサイズを変え、
「IconArea / S」「IconArea / M」「IconArea / L」とそれぞれ名前を付け、コンポーネント化。
コンポーネント化したら、製作した3つのIconAreaを選択して
右メニューよりVariantコンポーネントにします。

Valiantについては過去に記事を書いてますので合わせてお読みくださいhttps://blog.nasbi.jp/design/figma_variants/

3. アイコンを準備する

実際に表示したいSVGアイコンを用意し、
それぞれに「Icon / アイコン名」の名前を付けてから
コンポーネント化→まとめてVariantコンポーネントにします。

アイコンサイズは48pxにしています。

これで準備完了です。

インスタンスを置き換えてみよう

準備ができたら、あとはアセットからIconAreaを呼び出し、
IconArea / L を選択→「インスタンスの入れ替え」からアイコンを選ぶと…
アイコンがIconArea / L の大きさに可変し、インスタンスの入れ替えができました👏

ここで一つ上のIconAreaの方を選んで
「インスタンスの入れ替え」を選択しても、可変されないので注意です

普通の32pxの四角いコンポーネントを作り、インスタンスの入れ替えをしても
可変されず、元のアイコンの大きさ(48px)になってしまいます。

Slotを使うと可変されるけど…
ただのコンポーネントだと可変されず元の大きさになってしまう


Slotを使った管理方法は、
アイコンの数が多くなってしまう場合に有効なやりかたのひとつだとおもいます。
皆様も良きFigmaライフをお送りください。