Safari・iOS表示乱れ対策Tips <- TetraCalibers
TetraCalibers :
    -   for : science student & programmer
Safari・iOS表示乱れ対策Tips
    制作したサイトを実機で確認した時に、いつも悩ませられるのがSafariやiOSでの表示乱れ。 試行錯誤して見つけた解決策をまとめておきます。一例として試してみていただけたら幸いです。

     

    fontawesomeの一部のアイコンが表示されない

     

    iOSの全ブラウザとSafariだけ、一部のアイコンが表示されない場合がある。

     

    headタグにCDNリンクを貼っているにも関わらず表示されない場合は、次のことを試してみるとよい。

     

    対策1
    署名付きのCDNリンクを使用する

     

    例えば、https://cdnjs.com/libraries/font-awesome/5.15.4ボタンをクリックして取得したCDNリンクタグには、次のように謎の文字列が加えられている。

     

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    

     

    この中で特に重要なのがintegrity 属性で、これはブラウザ側が改ざん検知のために用いるものである。

     

    警戒心(?)の強いSafariは、この属性がついていないCDNリンクをブロックすることがあるらしい。

     

    対策2
    CDNリンクをHTMLファイルに貼るのではなく、CSSファイルの先頭で@import宣言を使う

     

    CSSファイルの先頭に
    @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css');
    
    などと加えることで、解決した事例もあるらしい。

     

    対策3
    fontawesomeのコード内の@font-face内にtext-rendering:optimizeLegibility;を追加

     

    これはもう最終手段。コードの改変を伴うので、これを行う場合は変数管理が可能なSCSSの使用を推奨する。
    以下、SCSSを使用している前提で解説する。

     https://github.com/FortAwesome/Font-Awesome にアクセスし、緑色のCodeボタンからZipファイルをダウンロードする。
    それを解凍したものをディレクトリごとSCSS作業ディレクトリに配置し、このうち、webfonts ディレクトリは公開ディレクトリに配置する。

     

    コンパイルするSCSSファイルの冒頭に、次のコードを追加する。(パスは一例)
    @import 'Font-Awesome/scss/fontawesome.scss';
    @import 'Font-Awesome/scss/solid.scss';
    @import 'Font-Awesome/scss/regular.scss';
    @import 'Font-Awesome/scss/brands.scss';
    

     solid.scssregular.scssbrands.scss 内の@font-face 内に、text-rendering: optimizeLegibility; を追加する。

     

    また、_variables.scss 内の次の行を書き換えておく。
    $fa-font-path: "/ドキュメントルートからwebfontsディレクトリへの相対パス" !default;
    

     

    これでコンパイルしてみて、表示を確かめる。

     

    この方法は一部の層に知られているが、実は私はこれでも問題が改善されず、最終的に次の修正を行うことで解決に至った。

     

    対策4
    _core.scss内のdisplay: inline-block;をコメントアウト(消去)

     

    アニメーションを実行するとチラつく(フリッカー現象)

     

    iPhoneのブラウザでアニメーションの伴う操作を実行すると、画面がチカチカっとチラつくことがある。 この現象をフリッカー現象という。

     

    例えば、dt 要素をクリックした時に、非表示にしていたdd 要素がスライドダウンで現れる、というドロップダウンメニューの実装を例に考えよう。

     

    対策1
    現れるdd要素自身にアニメーションを指定するのではなく、その中のコンテナ要素にアニメーションを指定する

     

    例えば、dd要素の内部要素をdiv要素で囲み、そのdiv要素にスライドダウンアニメーションを指定するなど。 これでうまくいった事例もあるらしい。

     

    対策2
    jQueryアニメーションをvelocity.jsに変える

     

    jQueryデフォルトのアニメーションは非常に重いため、jQueryと互換性があり、圧倒的に軽量なvelocity.js(http://velocityjs.org/)をおすすめする。 velocity.jsによるアニメーションに置き換えるだけで、かなりチラつきが軽減された。

     

    対策3
    キューを空にしてからアニメーションを実行する

     

    velocity.jsに置き換えてもチラつきが発生する場合、内部でアニメーション処理が渋滞(?)している可能性がある。
    アニメーションを実行する前に、現在実行中のアニメーションを完全にstopすることが、チラつき防止には非常に有効である。
    $('div').velocity("stop").velocity("slideDown", {
        easing: easing,
        duration: duration,
        // iPhoneでのチラつき防止 
        mobileHA: false 
    });
    

     

    jQueryならstop()メソッド、velocity.jsならvelocity("stop")をとりあえず加えておけばよい。

     

    対策4
    おまじないを加える

     

    次のコードは、stackoverflowなどでよく知られているおまじないだ。SCSSの例を示す。
    dd div {
        // チラつき防止
        perspective: 1000;
        backface-visibility: hidden;
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        overflow: hidden;
        transform-style: preserve-3d;
        
        & * {
            transform: translate3d(0, 0, 0);
        }
    }
    

     

    何やら、3D操作を加えることで、ブラウザがより高性能なレンダリング手法を選択するようになる…的な意味らしい。
    その分負荷がかかることも考えられるので、乱用すべきではない。

     

    ここまでの対策を行っても改善しない場合は、背景レンダリングとの相性についても考慮したほうが良い。

     

    そもそも、アニメーション実行時に背景がチカチカっと白光りする現象自体を解消しなくても、背景が元々白であればその現象にユーザは気づかないため、悩む必要がないのである。

     

    …しかし、アニメーションを使うなら全背景を白にしろ、というのは流石に横暴なので、以降は背景との共存テクニックを紹介する。

     

    対策5
    背景をCSSで描くのではなく、画像で指定する

     

    CSSグラデーションを何重にも重ねることは、複数枚の画像を重ねることと同義である。
    SVG等で同様の背景を作成し、1枚の画像として指定したほうがチラつきは軽減される。

     

    対策6
    背景画像専門の要素を作る

     

    例えば、画面全体に背景画像を指定する場合、背景画像を指定する要素は、画面に配置されるすべての要素を包む要素になるだろう。
    しかし、この構造に問題があるのである。
    背景画像の「上で」アニメーションを実行するから、アニメーション実行時にチカチカっと背景が乱れるのだ。

     

    アニメーションを指定する要素は、背景を指定する要素とは完全に分離したほうが良い。
    そうすれば、アニメーション実行による悪影響が背景には及ばなくなる。

     

    具体的には、次のように指定する。

     

    <div class="backImage"></div>
    <div class="container">
        <div class="parts"></div>
        <div class="animation"></div>
        <div class="parts"></div>
    </div>
    

     

    上のマークアップでは、背景画像専門の空要素を用意している。
    これをCSSにより、他の要素の下層かつ全面に広げる。

     

    // 要素包み専門要素
    .container {
        position: relative;
        z-index: 50;
    }
    
    // 背景専門要素
    .backImage {
        // 全面に広げる指定
        display: block;
        position: fixed;
        top: 0;
        bottom: 0;
        right: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        // 他要素の下層にする指定
        z-index: -1;
        // 背景の指定
        background: 任意の指定(画像でもグラデーションでもよい);
    }
    

     

    この手段をとることで、私は完全にチラつき現象から解放された。
    ここまで紹介したどの方法よりも圧倒的に効果がある方法であるように感じられる。(最初に言え)

     

    とはいえ、この手法を取り入れたとしても、対策2・3のようなアニメーション処理自体を軽くすることは忘れないほうが良い。

     

    背景画像の表示が意図した通りにならない

     

    現時点で、iOSは次のようなバグを抱えている。

     

    // この指定をすると背景画像が表示されない
    background-attachment: fixed;
    background-size: contain;
    

     

    // この指定をするとfixedが効かない
    background-attachment: fixed;
    background-size: cover;
    

     

    私が確認できているのはこの2パターンだが、もっとあるのかもしれない。

     

    この2パターンにおいては、結局background-attachment: fixed;が曲者だということになる。 ならば、別の方法でfixedを指定してやればよい。私たちはそのための手法を既に知っている。

     

    対応1
    背景画像専門の要素を作り、background-attachmentではなくpositionfixedを指定する

     

    PC版とスマホ版とで背景画像の表示が少々変わることを妥協するなら、次のような方法も考えられる。

     

    対応2
    fixedcontainの指定の代わりに、coverの指定を用いる

     

    具体的には、私は次のような指定でこの問題をごまかしたことがある。

     

    スマホサイズではiOSを意識した指定を加えている。coverによってfixedは効かなくなるが、coverしているために一応全面に背景画像は広がる、という結果を狙っている。
    .backImage {
        background-image: 画像;
        background-repeat: repeat-x;
        background-attachment: fixed;
        background-size: contain;
        background-position: bottom;
        
        @media (max-width: 993px) {
            background-image: 画像;
            background-size: cover;
            background-repeat: no-repeat;
        }
    }
    

     

    しかし、アニメーションを加える可能性を考えると、背景画像専門要素を作る手段を選ぶ方が一石二鳥となり賢明だろう。

     

    背景として指定したSVG画像が表示されない

     

    SafariやiOSではよくある現象。

     

    対策
    SVGをbase64エンコードしてURIとして指定する

     

    よく使っていたのがhttps://blog.s0014.com/posts/2017-01-19-il-to-svg/ というサイト。

     

    もし上記サイトでエンコードしてもプレビューが表示されない場合、SVGコードにDTDなどの余分なデータが含まれている可能性がある。
    この場合は、SVGOMGで最適化してからbase64エンコードを試みると良い。 (SVGOMGの最新の使い方はGoogle先生へ…)

     

    ところで、base64エンコードという手法には、注意点がいくつかある。

     

    まず、通常の画像はキャッシュの対象となるが、base64エンコードされた画像は、画像ではなく文字列として解釈されるため、キャッシュの対象とならない。 厳密に言えばその文字列を含むCSSファイル自体はキャッシュされるわけだが、毎度ブラウザ側でbase64文字列を画像に翻訳し直してから表示されることになるのである。

     

    そのため、間違ってもgif画像のような大容量の画像をbase64エンコードしようなどと考えてはならない。

     

    画像のbase64エンコードによるサイト高速化の恩恵は確かにあるが、大容量の画像の場合はその恩恵より翻訳時間が上回ってしまい、結局サイトは激遅になるのである。大容量の画像はそのまま送って普通にキャッシュされるべきだ。

     

    また、これは開発中の話だが、base64文字列をCSSファイルに貼り付けると、エディタが激重になるので注意。

     

    SCSSを使用している場合は、base64文字列を変数として定義する専用のファイルを作り、そのファイルをコンパイルするscssファイルの冒頭で@importして、それ以降は変数として扱った方が良い。

     

    そして、そのbase64を貼り付けたファイルは二度とエディタで開くな。base64文字列を表示するというタスクは他の処理を圧迫し、それを表示する度にエディタを再起動しなければやってられなくなるぞ。

     

    透明を指定した箇所が黒くなる

     

    大多数のブラウザはtransparentopacity(透明度)rgba(255, 255, 255, 透明度)(白の半透明)と解釈するが、iOSやSafariはrgba(0, 0, 0, 透明度)(黒の半透明)と解釈することが原因である。

     

    対策
    transparentrgba(背景色等, 0)として、
    opacity(透明度)rgba(背景色等, 透明度)として色を明示する

     

    フォームにCSSを加えても意図した通りに表示されない

     

    デフォルトのフォームデザインと衝突していることが原因のため、一旦デフォルトデザインを解除する必要がある。

     

    対策
    デザインを変更する各フォーム要素に-webkit-appearance: none;を追加

     

    filterプロパティを使った箇所の表示が乱れる

     

    3D操作を指定して、レンダリングモード変更を強制するとよい。

     

    対策
    filterプロパティを使う要素にはtransform: translateZ(0);を指定

     

    要素と要素を重ねた時、上の要素が思い通りの位置にならない

     

    これはiOSやSafariに限った話ではないが、よく使うTipsなので取り上げておきたい。

     

    縦横中央寄せしたい場合によく知られている手法の解説は、https://webdesignday.jp/inspiration/technique/css/3733/ がわかりやすいので、そちらに譲ることとする。

     

    さて、あまり知られていないが、個人的に推しているのは次のような方法だ。

     

    要素Aと要素Bを重ねる場合、次のようなマークアップを用意する。
    <div class="container">
        <div class="elementA"></div>
        <div class="elementB"></div>
    </div>
    

     

    重ねたい要素を包む親要素を用意し、親要素をグリッドレイアウトにする。
    重ねたい2つの要素の位置を同一セルにすることで、文字通り重なるようにする。

     

    .container {
        display: grid;
    }
    .elementA {
        grid-row: 1;
        grid-column: 1;
    }
    .elementB {
        grid-row: 1;
        grid-column: 1;
    }
    

     

    あとはjustify-contentalign-contentを指定することで、好きな位置に配置することができる。

     

    • justifyは横の位置指定
    • alignは縦の位置指定
    であり、それぞれの値については
    • startなら左or上寄せ
    • endなら右or下寄せ
    • centerなら中央寄せ
    という意味になる。

     

    例えば縦横中央寄せなら、次の指定を加えれば良い。
    .container {
        justify-content: center;
        align-content: center;
    }
    

     

    縦横中央寄せの指定は、次のようにかくこともできる。
    .elementA {
        justify-self: center;
        align-self: center;
    }
    .elementB {
        justify-self: center;
        align-self: center;
    }
    
    縦横中央寄せであれば冗長になるだけだが、要素ごとに細かく位置指定したい時には、-selfが便利である。

     

    グリッドレイアウトは新しい手法であるため、IE対応に関しては妥協する必要があるが、どの位置指定手法よりも柔軟で思い通りに動くため、個人的には一番気に入っている手法だ。

     

    改行指定が効かない

     

    例えば、word-break: break-all;がSafariやiOSブラウザで一部効かないことがある。

     

    この時、効く文字列と効かない文字列の違いは、ハイフン(-)を含むか否か、だった。

     

    対策
    hyphens: none;を追加する

     

    グラデーション文字が表示されない

     

    これはSafariのバグであり、次のいずれかを指定している要素では、background-clip:text;が効かなくなる。

     

    • display: block;
    • display: flex;
    • display: grid;

     

    インライン要素では正常に動作することが確認できた。

     

    対策
    display: inline;またはdisplay: inline-block;を指定する