CSS transitionによるレイアウトシフト

レイアウトシフトを引き起こすCSS transitionを特定して削除する方法を解説します。

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-10-17

CSS transitionがレイアウトシフトを引き起こす理由

CSS transitionによるCumulative Layout Shiftは、通常、ページの読み込みフェーズの早い段階で発生します。後から読み込まれたスタイルシートやスクリプトによってスタイルが変わると、widthやheightのtransitionがサイズの変更をアニメーション化し、下にあるコンテンツを一緒に動かしてしまいます。これらのシフトはすべてのページビューで発生するわけではありません。そのため、デバッグが困難になります。

2026年3月にArjen Karelが最終レビュー

transition: all の問題点

現場で最もよく見かける原因はtransition: allです。至るところで使われています。2022 Web AlmanacのCSSの章によると、Webページの53%がこれを使っており、他のどのtransitionプロパティよりも多く使われています。そのためwidthやheightもアニメーション化され、要素の周囲にあるコンテンツを動かしてしまいます。

CSS transitionは、プロパティ、duration、timing function、delayの4つの値で構成されます。ショートハンドで書くと以下のようになります:

/* property | duration | timing-function | delay */
transition: margin-right 4s ease-in-out 1s;

ホバー時の色をフェードさせたいだけなのに、要素にtransition: all .3s easeを指定してしまうことから問題が始まります。

ブラウザが要素に対して最初に計算したスタイルでは、transitionは実行されません。実行されるのは、後からスタイルが変更されたときだけです。そしてページの読み込み中には、これが頻繁に起こります。遅れて読み込まれるスタイルシートや、ナビゲーションメニューにクラスを追加するスクリプトがその例です。transition: allが指定されていると、ブラウザはサイズの変更までアニメーション化してしまいます。その結果、ファーストビューにあるメニューが300ミリ秒かけて広がり、その下にあるすべての要素を押し下げます。読み込み中にしか発生しないレイアウトシフトです。再現するのは簡単ではありません。

どのCSSプロパティがレイアウトシフトを引き起こすのか

すべてのCSS transitionがレイアウトシフトを引き起こすわけではありません。ブラウザにレイアウトの再計算を実行させるのは要素のサイズや位置を変更するプロパティだけであり、周囲のコンテンツを動かすのもレイアウト処理だけです。Googleの調査によると、レイアウトを発生させるCSSプロパティをアニメーション化しているページは、CLSの合格率が15%低下します。marginやborder-widthはさらに深刻です。これらをアニメーション化しているページでは、通常のほぼ2倍の割合でCLSが「不良」と判定されています。

以下のプロパティはいずれもレイアウトの再計算をトリガーするため、transitionを適用すると要素自体や周囲のコンテンツの位置をずらしてしまう可能性があります: width、height、margin、padding、top、left、right、bottom、border-width、font-size。

CLSに対して安全なプロパティ: transformとopacityはコンポジタで処理できます。filterやclip-pathの負荷も、最大でリペイントにとどまります。どちらもレイアウトをトリガーしません。

ペイントのみを発生させるため、同様にCLSに対して安全なプロパティ: background-color、color、box-shadow、outline。

重要なのはtransformとopacityの2つです。transformはレイアウト上の要素の位置を変更しないため、CLSでレイアウトシフトとしてカウントされることはありません。要素を移動させる必要がある場合は、topやleftではなくtransform: translate()を使ってください。拡大や縮小を行いたい場合は、widthやheightではなくtransform: scale()を使います。

ホバーだからといって例外ではありません。CLSは、クリック、タップ、キー入力の直後500ミリ秒以内に発生したシフトを無視しますが、要素の上にマウスを動かす操作はそのような入力には該当しません。そのため、widthに対するホバーtransitionはそのまま全量カウントされます。

下の動画は、読み込みフェーズ中にtransitionがレイアウトをシフトさせている様子を示しています。現場では日常茶飯事で見かける現象ですが、原因を突き止めるには少し調査が必要です。

CSS transitionを特定して修正する

CSS transitionに起因するレイアウトシフトをすべて特定するには、まずページ内のすべてのtransitionをリストアップし、レイアウトに影響を与えるプロパティをアニメーション化しているものを確認します。次にそれらを無効化または変更し、CLSが解消されるかを計測します。

Core Web Vitalsのヒント: これらのシフトはページ読み込みの初期に発生し、毎回のアクセスで起きるわけではありません。モバイル端末をエミュレートし、ネットワークをスロットリングして、キャッシュを無効化してください。読み込みが遅くなると、遅れて到着するスタイルシートやスクリプトが届く前にブラウザがページを描画するため、シフトがはるかに発生しやすくなります。

ステップ1: CSS transitionを特定する

手作業でCSS transitionを探すこともできます。すべてのスタイルシートを開き、「transition」という単語を検索してください。おそらく10分ほどかかります。もっと速い方法があります。次のスニペットをコンソールに貼り付けてEnterキーを押してください。

(() => {

  let nodeTable = [];
  let nodeArray = [];

  // Get the name of the node
  function getName(node) {
    const name = node.nodeName;
    return node.nodeType === 1
      ? name.toLowerCase()
      : name.toUpperCase().replace(/^#/, '');
  }

  // Get the selector
  const getSelector = (node) => {
    let sel = '';

    try {
      while (node && node.nodeType !== 9) {
        const el = node;
        const part = el.id
          ? '#' + el.id
          : getName(el) +
          (el.classList &&
            el.classList.value &&
            el.classList.value.trim() &&
            el.classList.value.trim().length
            ? '.' + el.classList.value.trim().replace(/\s+/g, '.')
            : '');
        if (sel.length + part.length > (100) - 1) return sel || part;
        sel = sel ? part + '>' + sel : part;
        if (el.id) break;
        node = el.parentNode;
      }
    } catch (err) {
      // Do nothing...
    }
    return sel;
  };

  const getNodesWithTransition = (node) => {

    // Get the computed style
    let cs = window.getComputedStyle(node);
    let tp = cs['transition-property'];
    let td = cs['transition-duration'];

    // If there is a transition, add it to the table
    if (tp !== '' && tp !== 'none' && td != '0s') {
      nodeTable.push({ selector: getSelector(node), transition: cs['transition'] });
      nodeArray.push(node);
    }

    // Recursively call this function for each child node
    for (let i = 0; i < node.children.length; i++) {
      getNodesWithTransition(node.children[i]);
    }
  }

  // find all transitions
  getNodesWithTransition(document.body);

  // Display the results in the console
  console.log('%cReadable table of selectors and their transitions', 'color: red; font-weight: bold;');
  console.table(nodeTable);

  console.log('%cNodeList for you to inspect (harder to read but more info)', 'color: red; font-weight: bold;');
  console.log(nodeArray);


  // styles to temporarity override the transitions
  let selectors = nodeTable.map((item) => item.selector).join(', ');

  console.log('%cSpecific CSS to disable all transitions on this page', 'color: red; font-weight: bold;');
  console.log(`<style>${selectors}{transition-property: none !important;}</style>`);

  console.log('%cGlobal CSS to disable all transitions on this page (not suggested on production)', 'color: red; font-weight: bold;');
  console.log(`<style>*{transition-property: none !important;}</style>`);

})()

ページ上のすべてのtransitionと、それが設定されている要素の一覧テーブルが出力されます。

cls snippet table

次にtransition列を確認します。まずはallと書かれたすべての行からチェックしてください。これにはwidthやheightをはじめ、アニメーション可能なあらゆるプロパティが含まれます。続いて、widthやheightが直接指定されている行を探します。marginやpaddingもコンテンツを動かしますし、topやleftも同様です。

ステップ2: CSS transitionを修正する

このスニペットは、それらのtransitionを無効化するためのCSSも出力します。手早くテストするなら、簡単な方法として次の1行ですべてのtransitionを無効化できます:

<style>*{transition-property: none !important;}</style>

本番サイトでは、もう少し丁寧な対応が必要です。セレクタごとに、不要なtransitionプロパティのみを削除してください。たとえば、#button{transition: all .2s}を#button{transition: background-color .2s}に変更します。

ステップ3: レイアウトシフトの変化を計測する

最後のステップは計測です。Chrome拡張機能の「Core Web Vitals Visualizer」を使えば、手元の画面でシフトを視覚化できます。また、CoreDashのようなRUMツールを使えば、実際の訪問者に対してもシフトが解消されたかを確認できます。CoreDashでは、シフトした要素ごとにCLSの内訳を確認できます。修正後は、問題のメニューやバナーがリストから消え、モバイルのp75もそれに続くはずです。

layout shift transition measured by coredash

transitionのベストプラクティス

  1. 対象のプロパティを正確に指定してください。transition: allではなくtransition: background-color .2s easeのように記述すれば、意図しないプロパティがアニメーション化されてシフトを引き起こすことはなくなります。
  2. transformとopacityをアニメーション化してください。ブラウザはどちらもコンポジタで処理でき、どちらもレイアウトをトリガーしません。要素を動かすにはtransform: translate()、サイズを変更するにはtransform: scale()を使用してください。フェードにはopacityを使います。これはGoogleが推奨している内容でもあります。
  3. transitionを適用せざるを得ない要素には、変化の前後の両方で明示的なwidthとheight、あるいはアスペクト比を指定し、周囲のコンテンツが動かないようにしてください。詳しくは自動サイズ調整される画像によるレイアウトシフトの修正を参照してください。
  4. will-changeは控えめに使ってください。これは要素を独自のコンポジタレイヤーに昇格させ、アニメーションの準備をするようブラウザに伝えるプロパティです。しかし、それには代償が伴います。will-change: transformを指定すると、その要素に新たなスタッキングコンテキストが作成され、位置指定された子孫要素の包含ブロックになります。さらに、レイヤーが増えるたびにGPUメモリを消費します。アニメーションが開始される直前にJavaScriptで付与し、終了したら削除してください。スタイルシートに常時残したままにしてはいけません。

いまだに多くのサイトでこの問題が見られます。2025 Web Almanacによると、モバイルページの40%でコンポジタ非対応のアニメーションが使われています。CLSはCore Web Vitalsの中で最も合格率が高く、モバイルでは81%です。もし不合格の19%に入っているなら、CSS transitionがまだ確認していない盲点かもしれません。

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

大規模サイトのCore Web Vitalsを通してきました。

欧州の大手メディアやEコマース基盤で50万ページ超。修正コードを書き、フィールドデータで効果を検証します。

進め方を見る
CSS transitionによるレイアウトシフト Core Web Vitals CSS transitionによるレイアウトシフト