Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
19
Help us understand the problem. What is going on with this article?
@ota-meshi

Mobile Safariでの100vhが気に入らないからPostCSSで解決した

More than 1 year has passed since last update.

CSSのvhとかのviewport unitsって画面ぴったりな要素や画面サイズに合わせた要素が簡単に作れて便利ですよね。

しかし残念ながらvhはMobile Safariだとぴったりいかない場合があります。
これはアドレスバーの高さを計算に入れる入れないの問題に起因しているようです。

色々な解決策

Mobile Safariで100vhを画面ぴったりにしたい場合、Stack Overflowには色々な解決策があります。

Stack Overflow - How to fix vh(viewport unit) css in mobile Safari?

  1. Viewport Units Buggyfill使う
    ちゃんと試せてないけど、実行時にvh単位の値を上書きするCSSを付与する様子。

  2. jQueryで対応する

    $('.my-element').height(window.innerHeight + 'px');
    
  3. JavaScriptで対応する

    document.getElementById("my-element").style.height = window.innerHeight + 'px';
    
  4. height: 100%引き回す
    古き良き方法。

  5. CSS カスタムプロパティで対応する

    .my-element {
      height: 100vh; /* Fallback for browsers that do not support Custom Properties */
      height: calc(var(--vh, 1vh) * 100);
    }
    
    // First we get the viewport height and we multiple it by 1% to get a value for a vh unit
    let vh = window.innerHeight * 0.01;
    // Then we set the value in the --vh custom property to the root of the document
    document.documentElement.style.setProperty('--vh', `${vh}px`);
    

    CSS-Tricks - The trick to viewport units on mobile

で、最後のCSS-Tricksの解決策が個人的にいいなーと思ったのですが、100vh書いたところ全部にcalc(var(--vh, 1vh) * 100);って書いていくのめんどくさいなー。PostCSSでなんとかなるんじゃないかなー。というのがこの先の解決策です。

CSS-Tricks - The trick to viewport units on mobileをPostCSSで実施する

PostCSSプラグインを作る

ローカルに自作のPostCSSプラグインを作ります。

postcss-viewport-units-on-mobile.js
const postcss = require('postcss')

// プラグイン定義
module.exports = postcss.plugin('postcss-viewport-units-on-mobile', () =>
  createPlugin()
)

function createPlugin() {
  return root => {
    const replaces = []
    // CSSの宣言(declaration)を走査
    root.walkDecls(decl => {
      // vh,vmax,vminの単位で宣言した値を書き換えたCSS値を作る
      const newValue = decl.value.replace(
        /\b([-+]?[\d.]+)(vh|vmax|vmin)\b/g,
        replacer
      )
      if (decl.value !== newValue) {
        // vh,vmax,vminが書き換えられていたら、そのCSSを保持しておく
        replaces.push({ decl, newValue })
      }
    })
    // vh,vmax,vminが書き換えられたCSSの宣言を元のCSSの宣言の後ろに挿入していく
    for (const { decl, newValue } of replaces) {
      decl.parent.insertAfter(decl, decl.clone({ value: newValue }))
    }
  }
}

// vh,vmax,vminの書き換え
function replacer(original, snum, unit) {
  if (isNaN(snum)) {
    return original
  }
  const num = snum - 0
  return `calc(${snum} * var(--${unit}, 1${unit}))`
}

このPostCSSプラグインはvhを使った単位を見つけたら、その下にCSS カスタムプロパティで書き換えたCSSを付与します。

つまり、このCSSは

before.css
.a {
  height: 100vh;
}
.b {
  height: calc(100vh - 10px);
}

こうなります。

after.css
.a {
  height: 100vh;
  height: calc(100 * var(--vh, 1vh));
}
.b {
  height: calc(100vh - 10px);
  height: calc(calc(100 * var(--vh, 1vh)) - 10px);
}

PostCSSの処理に入れる

PostCSSの使い方次第なので、これだ!っていうのを1つ書くのはできませんが、
postcss.config.jsに書く形だとこうですかね。

postcss.config.js
module.exports = {
  plugins: [
    // ... 
    // require('autoprefixer'),
    // ... 
    require('./path/to/postcss-viewport-units-on-mobile.js')(), // 作ったプラグイン
    // ... 
  ]
}

アプリケーションにJavaScriptを埋め込む

次のJavaScriptをアプリケーションに埋め込みます。

updateViewport()

window.addEventListener('resize', updateViewport)

function updateViewport() {
  const vh = window.innerHeight / 100
  const vw = window.innerWidth / 100

  const root = document.documentElement

  // 各カスタムプロパティに`window.innerHeight / 100`,`window.innerWidth / 100`の値をセット
  root.style.setProperty('--vh', `${vh}px`)
  if (vh > vw) {
    root.style.setProperty('--vmax', `${vh}px`)
    root.style.setProperty('--vmin', `${vw}px`)
  } else {
    root.style.setProperty('--vmax', `${vw}px`)
    root.style.setProperty('--vmin', `${vh}px`)
  }
}

実行結果

記述したCSS

.my-element {
  height: 100vh;
}

は、次のように変換され、

.my-element {
  height: 100vh;
  height: calc(100 * var(--vh, 1vh));
}

JavaScriptで、CSSカスタムプロパティ--vhwindow.innerHeight / 100をセットすると、
.my-elementの高さがwindow.innerHeightと同じ高さになり、Mobile Safariでもいい感じに画面に収まります!

まとめ

CSSを自分で書き換えなくても、CSS-Tricks - The trick to viewport units on mobileのトリックが使えます。そうPostCSSならね。

19
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ota-meshi
会計パッケージ開発10年、独立系SIer5年経験。6歳と1歳の娘と格闘の毎日のJSON色つけ係です。Java8+、ES6+、Vue、ESLint、stylelint、postcss、python、webpack。会社のOSS CheetahGridのメンテナ。Vue.jsのメンバー。stylelintのメンバー。自分の備忘録としても使うので小さい情報も書いていきます。
future
ITを武器とした課題解決型のコンサルティングサービスを提供します

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
19
Help us understand the problem. What is going on with this article?