🐝

UI実装で遭遇する“あるあるバグ”とその解決方法

に公開

こんにちは。
みなさんUI実装していますか?
実際のプロダクトでは小さなUI改善が多く、凝ったレイアウトや大規模なUI実装をすることはあまりないのではないでしょうか?

今回私も久しぶりに業務で大規模なデザインリニューアル作業を行い特定のブラウザでだけ起こるバグやCSSの罠に悩まされました。

なので、開発中に遭遇したUIバグとその時に取った解決方法をまとめたTIPS集を紹介します。

SafariでSVGがぼやける

現象

<img src="xxx.svg"> でSVGを読み込んだところ、Safariでだけ画像がぼやけて表示される。

期待する状態とぼやける状態を比べてみます。

期待する状態 ぼやける状態

文字や画像がぼやけて見えるのがおわかりいただけたでしょうか...

原因

どうやらシャドウが効いたSVGの場合に発生するSafariのバグのようです。
同じSVGでシャドウを無くしてみると、<img src="xxx.svg">で表示してもぼやけることなく綺麗に見えています。

今回使用したSVGは下記ですが、シャドウを指定しているのは<feDropShadow dx="6" dy="6" stdDeviation="6" flood-color="black" />の部分になります。

<svg
  width="300"
  height="300"
  viewBox="0 0 300 300"
  xmlns="http://www.w3.org/2000/svg"
>
  <defs>
    <filter id="shadow">
      <feDropShadow dx="6" dy="6" stdDeviation="6" flood-color="black" />
    </filter>
  </defs>
  <rect
    x="30"
    y="30"
    width="240"
    height="240"
    rx="30"
    ry="30"
    fill="#4CAF50"
    filter="url(#shadow)"
  />
  <text
    x="150"
    y="180"
    font-size="40"
    text-anchor="middle"
    fill="white"
    filter="url(#shadow)"
  >
    Shadow
  </text>
</svg>

<feDropShadow>要素の詳細は下記の通りです。

  • feDropShadow... SVGフィルターの一種で、要素にドロップシャドウ(影)効果を付ける
  • dx="6"...影を右方向に6px移動させる(X軸方向のオフセット)
  • dy="6"...影を下方向に6px移動させる(Y軸方向のオフセット)
  • stdDeviation="6"...シャドウのぼかし半径(ブラーの強さ)を指定。値が大きいほどふんわりした影になる。
  • flood-color="black"...シャドウの色を黒に指定。影の色を設定するための属性。

なんとなくstdDeviationが怪しそうですね。
stdDeviationを 0, 2, 4, 6, 10 で比較して、見た目がどう変わるか確認してみます。

SVG
<svg width="800" height="220" viewBox="0 0 800 220" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="shadow0">
      <feDropShadow dx="6" dy="6" stdDeviation="0" flood-color="black" />
    </filter>
    <filter id="shadow2">
      <feDropShadow dx="6" dy="6" stdDeviation="2" flood-color="black" />
    </filter>
    <filter id="shadow4">
      <feDropShadow dx="6" dy="6" stdDeviation="4" flood-color="black" />
    </filter>
    <filter id="shadow6">
      <feDropShadow dx="6" dy="6" stdDeviation="6" flood-color="black" />
    </filter>
    <filter id="shadow10">
      <feDropShadow dx="6" dy="6" stdDeviation="10" flood-color="black" />
    </filter>
  </defs>

  <!-- Labels -->
  <text x="50" y="30" font-size="16">stdDeviation=0</text>
  <text x="200" y="30" font-size="16">stdDeviation=2</text>
  <text x="350" y="30" font-size="16">stdDeviation=4</text>
  <text x="500" y="30" font-size="16">stdDeviation=6</text>
  <text x="650" y="30" font-size="16">stdDeviation=10</text>

  <!-- Rectangles with shadows -->
  <rect x="30" y="50" width="100" height="100" fill="#4CAF50" filter="url(#shadow0)" rx="10" ry="10" />
  <rect x="180" y="50" width="100" height="100" fill="#4CAF50" filter="url(#shadow2)" rx="10" ry="10" />
  <rect x="330" y="50" width="100" height="100" fill="#4CAF50" filter="url(#shadow4)" rx="10" ry="10" />
  <rect x="480" y="50" width="100" height="100" fill="#4CAF50" filter="url(#shadow6)" rx="10" ry="10" />
  <rect x="630" y="50" width="100" height="100" fill="#4CAF50" filter="url(#shadow10)" rx="10" ry="10" />
</svg>

stdDeviation0の時は鮮明に見えているのに比べ、値が大きくなるとぼやけていきますね。
以上のことからシャドウのぼかし半径を指定するstdDeviationによってぼやけてしまうことがわかりました。

解決方法

解決方法は2つあります。
ここまでぼやける原因を書きましたが、解決方法はSVGのシャドウを変更するなどの方法ではありません。笑

1つめは、SVGをインラインで読み込む方法です。
具体的には、HTML内に直接<svg>...</svg>を書きます。

<img src="icon.svg" alt="icon">

<img>で記載の部分を下記のように変更します。

<svg viewBox="...">...</svg>

2つ目は、SVGのサイズを拡大してtransformで元のサイズに戻す方法です。

export const Example = () => {
  return (
    <div className="container">
      <img
        src="/example.svg"
        alt="shadow"
        className="image"
      />
    </div>
  );
};

.container {
  width: 300px;
  height: 300px;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
}

.image {
  width: 600px;
  height: 600px;
  transform: scale(0.5);
}

元の画像サイズが300 * 300なので、それを2倍のサイズで指定してtransform: scale(0.5)で元のサイズに戻しています。
また、imgをラップするDOMを作成し、imgを中央寄せにしてoverflow: hiddenを指定することで表示できます。

Flexの子要素で3点リーダーが効かない

現象

display: flex を使ったレイアウトで、テキストを3点リーダー(ellipsis)で省略したいのに効かないケース。
例えば下記のように固定幅と自由幅を横並びにしてflex: 1としても3点リーダーで省略されない場合があります。

コードは下記の通りです。

export const Example = () => (
  <div className="container">
    <p className="fixed">
      固定サイズ
    </p>
    <div className="flex">
      <p className="text">
        春はるは、あけぼの。やうやうしろくなりゆく山やまぎは、すこし明あかりて、紫むらさきだちたる雲くもの、細ほそくたなびきたる。
      </p>
    </div>
  </div>
)

.container {
    display: flex;
}
.fixed {
    width: 100px;
    background-color: #ddd;
    padding: 20px;
}
.flex {
  flex: 1;
  padding: 20px;
}
.text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

原因

flexの子要素に対してtext-overflow: ellipsisを効かせるには、オーバーフローの指定をする必要があります。

解決方法

3点リーダーにしたい親要素にoverflow: 'hidden'を指定します。

.container {
    display: flex;
}
.fixed {
    width: 100px;
    background-color: #ddd;
    padding: 20px;
}
.flex {
    flex: 1;
    padding: 20px;
    overflow: hidden; // ここにoverflow: hiddenを追加
}
.text {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

これで意図した通りflexの子要素に3点リーダーを指定することができました。

iOS Safariで画面の高さいっぱいにするために100vhを指定すると余白ができる

現象

iOS Safariで、フルスクリーンモーダルなどの画面の高さいっぱいの指定をする場合に height: 100vh を指定すると余白ができてしまう。


画面の高さちょうどにしたいのに、下に余白ができてしまいスクロールできてしまいます。

コードは下記の通りです。

export const Example = () => {
  return (
    <div className="container">
      <h1>タイトル</h1>
      <p>コンテンツ</p>
    </div>
  );
};

.container {
    background-color: #ddd;
    height: 100vh;
}

原因

iOS Safariではアドレスバーなどの表示により、ビューポートの高さ(100vh)がブラウザUI(アドレスバーやツールバー)を含んだ高さとして計算されます。
iOSではスクロール操作などによりアドレスバーが表示・非表示に変化しますが、100vh は常に初期のビューポートサイズ(アドレスバーが表示されている状態)を基準として計算されます。

そのため、実際の表示領域(=ユーザーが見ている部分)よりも高くなり、結果として余白やズレが生じてしまいます。

解決方法

100dvh(dynamic viewport height)を使う。
100dvhは、アドレスバーなどのUIを除いた「現在の見えている範囲」を基準として高さを決定するため、フルスクリーンのレイアウトに正確にフィットするようになります。

.container {
    background-color: #ddd;
    height: 100dvh; // dvhを指定する
}

これで余分な余白が消え画面の高さがいっぱいになりました。

ブラウザのサポート状況は下記の通りで、モダンな主要ブラウザでは対応しています。
https://caniuse.com/viewport-unit-variants

おわりに

今回紹介したTIPSはどれも軽微なものです。
ただ各ブラウザの挙動の違いやCSSプロパティの罠は見落としがちで、かつ修正に時間がかかり時間が溶けていくことも多いので開発中に「なんか変だな」と思ったときにこの記事が少しでも役に立てば嬉しいです。

スペースマーケット Engineer Blog

Discussion