0

I build a SPA that should always take 100% of screen height with the target browser group - iOS(mobile) Safari. height:100vh doesn't work properly on iOS Safari -

CSS3 100vh not constant in mobile browser

https://css-tricks.com/css-fix-for-100vh-in-mobile-webkit/

A suggested solution on some resources to use -webkit-fill-available didn't help.

Therefore, I decided to control the height of the app container with JS:

const [height, setHeight] = useState(window.innerHeight);

  const handleWindowSizeChange = () => {
    setHeight(window.innerHeight);
  };
  useEffect(() => {
    window.addEventListener("resize", handleWindowSizeChange);
    return () => window.removeEventListener("resize", handleWindowSizeChange);
  }, []);
  return (
    <div className="App" style={{ height: height }}>
      <header className="top"></header>
      <div className="main"></div>
      <footer className="bottom"><button>Click</button></footer>
    </div>

My solution works until we rotate the screen from portrait orientation to landscape, and back to portrait. After these rotations, we have a gap at the bottom of the screen view, right below the app footer. The same behaviour can be noticed if we open a demo - https://react-div-100vh.vercel.app/ for a package which is supposed to solve the issue, and make the same screen rotations.

Browser: Safari 14.6 iOS/iPhone 7

Repository

Live app

CodeSandbox

Dmitriif
  • 2,020
  • 9
  • 20

3 Answers3

0

Viewport height is tricky on iOS Safari.

Depending on the use case, -webkit-fill-available could work (see article).

I mostly have to use this JavaScript/CSS var fix:

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

<script type="application/javascript">
  function handleResize () { window.document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`); }
  window.onresize = handleResize
  handleResize()
</script>

Inspiration: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/

Tom Söderlund
  • 4,743
  • 4
  • 45
  • 67
0
import React from 'react'

const getHeight = () => {
    const html = document?.documentElement
    return Math.max(window?.innerHeight, html?.clientHeight, html?.offsetHeight)
}

const getWidth = () => {
    const html = document?.documentElement
    return Math.max(window?.innerWidth, html?.clientWidth, html?.offsetWidth)
}

const isIPad = (platform, maxTouchPoints) =>
    maxTouchPoints && maxTouchPoints > 2 && /MacIntel/.test(platform)

const isIPhoneOrIPad = (platform, touchpoints) =>
    platform === 'iPhone' ||
    /iPad/.test(platform) ||
    isIPad(platform, touchpoints)

const isIDevice = isIPhoneOrIPad(
    window?.navigator?.platform,
    window?.navigator?.maxTouchPoints,
)

const isIOSPWA = window.navigator['standalone'] === true

const MyApp = (props) => {
    const rafTimer = React.useRef(null)
    const timer = React.useRef(null)

    React.useEffect(() => {
        if (!isIDevice) return

        const iosHack = () => {
            const height = getHeight()
            const width = getWidth()
            document.body.style.minHeight = `${height}px`
            document.body.style.maxHeight = `${height}px`
            if (!isIOSPWA && height > width) {
                clearTimeout(timer.current)
                timer.current = setTimeout(() => {
                    const height = getHeight()
                    document.body.style.minHeight = `${height}px`
                    document.body.style.maxHeight = `${height}px`
                    clearTimeout(timer.current)
                    timer.current = setTimeout(() => {
                        const height = getHeight()
                        document.body.style.minHeight = `${height}px`
                        document.body.style.maxHeight = `${height}px`
                    }, 300)
                }, 300)
            }
        }

        cancelAnimationFrame(rafTimer.current)
        rafTimer.current = requestAnimationFrame(iosHack)

        return () => {
            cancelAnimationFrame(rafTimer.current)
            clearTimeout(timer.current)
        }
    })

    return <div>{props.children}</div>
}

export default MyApp
SCG82
  • 85
  • 1
  • 6
0

The solution was simply to use % rather than vh for HTML, Body and #root(in case or react-apps created with create-react-app) elements:

html,
body,
#root {
  height: 100%;
}

After this, the content inside the root element takes the entire height of a screen and will adapt when browser tabs appear/disappear. (Tested on iOS Safari 15.3.1 and Chrome iOS v99.0.4844.59)

Credit: The article about CSS Reset from and the course of the same author

Dmitriif
  • 2,020
  • 9
  • 20