头图

漫谈移动端 viewport 视口设置的一些用途以及可能遭遇的各种问题

我们通过 viewport 视口设置,可以实现对移动端设备窗口的尺寸控制,这是个十分有用的功能,并且由于现在 viewport 已经支持动态修改了,所以还能在页面加载之后再根据需求对其进行调整,但实际上这个设置,尤其是动态修改,是存在许多问题的,XJ 为了解决这些问题,甚至还特意写了个 xj.viewport 插件,当然在这篇文章中插件不是重点,但插件的开发(踩坑)过程却是这篇文章诞生的基础。

本文主要分为三部分,一是讲 XJ 在开发过程中对于 viewport 的一些额外认知,二是讲利用 viewport 在移动端进行一些有用的尺寸设置,三是讲在移动端进行 viewport 设置时可能会遭遇到的各种 BUG 和解决方案,如果你之前对 viewport 的认识仅限于在 <head> 中设置 <meta name="viewport" content="width=device-width,initial-scale=1" />,那么阅读本篇文章后一定会有些意外收获的。


01. 一些 viewport 相关的基础性知识点

以下内容是 XJ 在开发实践中对 viewport 视口的一些发现和理解,涉及到了基础属性的一些设置细节以及使用建议,这里需要提前说明的是,下面我们提到设备的各种尺寸,都是指理想尺寸而不是设备分辨率(理想尺寸又被称为 CSS 像素),例如 iPhone8 的理想尺寸是 375 x 667 而设备分辨率是 750 x 1334,这涉及到设备像素比既 DPR 问题,你可以参考 移动 web 开发之像素和 DPR 这篇文章。

————

01.01. 同时设置 width 和 initial-scale 属性

width 属性用于定义窗口的宽度,可以是个明确的数值如 500,也可以设置为设备的原始宽度既 device-width,如 iPhone4 的屏幕宽高为 320 x 480device-width 在 iPhone4 中就是 320px,而 iPhone8 的屏幕宽高是 375 x 667device-width 在 iPhone8 中就 375px,也就是说 device-width 是个相对尺寸,在不同型号的设备中并不相同,它取决于设备的原始宽度尺寸。

initial-scale 属性用于设置窗口的初始比例,但它是并非是以当前 width 属性的设置作为基础,而是以 device-width 既设备的原始宽度作为依据,例如在 iPhone8 中,initial-scale=1 则窗口宽度就是 375px,因为 375/1=375initial-scale=0.75 则窗口宽度就是 500px,因为 375/0.75=500,很多人都误以为 initial-scale 属性是以当前的 width 为基础,这是不对的。

那么窗口宽度究竟是由 width 属性还是 initial-scale 属性决定的?实际上这是个兼容问题,有的环境是只设置 width 属性就够了,有的环境是只设置 initial-scale 属性就够了,但是考虑到兼容,最好是同时设置并保持一致,例如说想在 iPhone8 实现宽度尺寸为 500px,则 meta 标签的 content 属性就该设置为 width=500,initial-scale=0.75,这个 0.75 来自于 375/500

那么当这两个属性不同时会怎样?根据实测是浏览器会以大的那个值为准,例如在 iPhone8 设置 width=500,initial-scale=1,这种情况下窗口宽度会等于 500px,但初始进入页面可能依然会以设备的原始尺寸既 375px 来显示(在真实设备中很可能会这样),因为 initial-scale=1,这个 1 就是 device-width375px,此时界面无法展示所有内容,得横向滚动才能看到右侧的东西。

这个时候就需要用户对界面进行捏合操作了,得缩小窗口既将 scale 缩小为 0.75 后窗口的宽度才会变成 500px,多一步操作总是不理想的(虽然浏览器会记住这个尺寸操作),但每次都需要计算后再设置合适的 initial-scale 属性也很烦,这其实也是 XJ 写 xj.viewport 插件的起因,该插件可通过简单的设置让 initial-scale 属性和 width 属性自动保持一致,省去了手动计算的麻烦。

最后再提一点,在做 Demo 测试的时候,查看当前窗口的尺寸是比较容易的,如果是使用 Chrome 的移动模拟功能,那么直接审查元素即可,如果是在移动端,那么用 document.documentElement.clientWidth 属性也可以得知,但是当前窗口的比例既 scale 缩放就不好知晓,好在较新的浏览器现在都已支持 visualViewport 对象,所以我们使用 visualViewport.scale 属性就可以知道窗口比例了。

————

01.02. 间接实现 height 属性以定义窗口的高度

height 属性和 device-height 属性值,其实也在 viewport 的标准之中,只是到目前为止,从来就没有任何浏览器实现过它罢了,标准和实际不相符是前端的特色嘛,但不支持不代表我们就没法实现,如果你想定义移动端窗口的高度,可通过 widthinitial-scale 属性来实现,因为移动设备的屏幕,宽高比例是固定的,也就是说在一定的宽度下就会有对应的一个高度,实现方法就在此中。

例如 iPhone8 的屏幕分辨率是 375 x 667,如果想让窗口的高度为 800px(这里暂不考虑地址栏和工具栏对屏幕尺寸的影响),那么 viewport 设为 width=450,initial-scale=0.8333 即可,450 源自 375 * 800 / 6670.8333 源自 375/450,宽度计算说白了就只是个 "Cross Multiplication" 既 交叉相乘 而已,这是小学数学的知识点,忘记可百度一下来温故知新,这里就不解释了。

将窗口宽度设置为我们想要的那个目标宽度,由于移动端屏幕是具有固定比例的,所以自然可得到我们想要的目标高度,理论上来讲是这样,但实际上这个高度很可能是不精准的,因为高度是 width 属性和 initial-scale 属性根据等比例原理计算出来的,而有些浏览器并不支持 width 属性的小数那部分(可能被四舍也可能被五入),所以得出来的高度值可有能跟目标值有 ±1±2 的偏差。

些许偏差倒还没什么,更麻烦的是移动端有些浏览器如 Safari(IOS) 在页面滚动时,会出现地址栏和工具栏的收起或放下,这就导致了移动端页面的高度会频繁变化,但我们并不能跟着变化响应,因为重设 viewport 会引起页面的回流和重绘,此时页面就会发生抖动或闪烁,甚至窗口的比例都会被重置,这种情况下用户体验就会很糟,所以总结就是,窗口高度可被定义但不精准,如何响应也是个大问题。

————

01.03. 别设置 user-scalable=no 禁止用户缩放

user-scalable 属性用于控制用户是否可对界面进行缩放,minimum-scalemaximum-scale 属性则用于定义缩放的最小值和最大值,但实际上这三个属性在 IOS10+ 中就不再被支持了,因为 Safari 的开发团队认禁用缩放功能和限制缩放级别会影响访问性,导致视力不佳的人难以阅读和理解页面内容,虽然禁止缩放会让页面变得更像 APP,但能缩放本来是 HTML 的一种优势,干嘛要像 APP 呢?

不建议设置这三个属性来禁止缩放,还有另一个原因是,移动设备可能存在屏幕旋转既 orientation 的操作,而屏幕旋转后窗口的尺寸就可能发生变化,窗口比例也可能需要重新调整,此时就需要用户进行捏合操作将界面缩放到合适尺寸,如果 user-scalable 被设置为 nominimum-scalemaximum-scale 被设置为 1(也会导致不可缩放),则界面可能就会一直保持一个不理想的状态。


02. 利用 viewport 来进行各种尺寸设置

下面是使用 viewport 来设置移动端窗口尺寸的一些案例,需要注意的是,不使用插件的 Demo 并没有做 resize 事件监听,所以得按 F5 刷新才能重置尺寸,实际上这里的 resize 事件并不好监听,因为设置视窗的 content 属性也会引起 resize 事件,导致事件可能进入死循环,更不要说移动端窗口的尺寸有诸多问题和 BUG,如果把这些问题全考虑在内,代码会变得十分复杂,所以只能简化了。

————

02.01. 为视窗设置一个固定宽度

在下面的例子中,我们通过动态计算,将视窗的宽度固定为 512px,不管设备的原始宽度是多少,也不管是竖屏或横屏模式,反正设备的宽度尺寸现在总是 512px 了,但固定窗口的宽度尺寸有什么用呢?实际上并没有什么用,大屏设备如 ipad 遭遇到一个小尺寸,会让内容显得臃肿,小屏设备如 iPhone8 遭遇到一个大尺寸,会让内容过小而不看不清,这个例子只是简单展示如何动态设置尺寸罢了。

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <style>body{margin:0;font:20px/1.5 Menlo,Consolas,"Hiragino Sans GB","Microsoft YaHei",Monospace;}</style>
    </head>
    <body>
        <div id="windowSize">width=512/height=512</div>
        <div id="viewportContent">width=512,initial-scale=1</div>
        
        <script>
        var ele_html = document.querySelector('html');
        var ele_meta = document.querySelector('meta[name="viewport"]');
        ele_meta.content = 'width=512,initial-scale=' + (ele_html.clientWidth / 512);
        
        document.getElementById('windowSize').textContent = 
        'width='+ele_html.clientWidth+'/height='+ele_html.clientHeight;
        document.getElementById('viewportContent').textContent = ele_meta.content;
        </script>
    </body>
</html>

↓ View & Code ↑

上面这个 Demo 并没有做 resize 事件监听,所以得手动刷新才行,在本文的最后一章我们会提到设置 viewport 视口将会遭遇的各种 BUG 和问题,到时你就会知道为什么 XJ 没在这里监听 resize 事件了,如果你懒得自己解决兼容问题,或是想节约开发时间,也许可以试试 xj.viewport 插件,它使用起来十分简单,并且由于解决了兼容问题,所以能自动响应无需手动刷新,下面是一个简单的例子。

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <!-- 在 meta 后引入 xj.viewport 插件,然后在 meta 设置 xj-viewport="{width:512}" 即可 -->
        <meta name="viewport" xj-viewport="{width:512}" content="width=device-width,initial-scale=1" />
        <script src="https://cdn.jsdelivr.net/gh/xjZone/xj.viewport@0.3.2/dist/xj.viewport.min.js"></script>
        <style>body{margin:0;font:20px/1.5 Menlo,Consolas,"Hiragino Sans GB","Microsoft YaHei",Monospace;}</style>
    </head>
    <body>
        <div id="windowSize">width=512/height=512</div>
        <div id="viewportContent">width=512,initial-scale=1</div>
        <script>
        var ele_windowSize = document.getElementById('windowSize');
        var ele_viewportContent = document.getElementById('viewportContent');
        
        window.addEventListener('resize', function(){
            ele_windowSize.textContent = 'width=' + xj.viewport.
            width() + '/height=' + xj.viewport.height();
            ele_viewportContent.textContent = xj.viewport.get();
        }, false);
        </script>
    </body>
</html>

↓ View & Code ↑

————

02.02. 为视窗设置一个最小宽度

在下面的例子中,如果视窗的宽度小于 416px 就设置为 416px,如果视窗的宽度大于 416px 则不做处理,你肯定会问设置一个最小宽度有什么用?这个就很有用了,例如说你拿到的设计稿宽度是 375px,但实际上市场里还是存在一些窗口宽度不足 375px 的设备,例如说 iPhone5/5C/SE,窗口宽度就只有 320px,此时将最小宽度设置为 375px,就不用担心小屏设备出现尺寸不足的问题了。

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <style>body{margin:0;font:20px/1.5 Menlo,Consolas,"Hiragino Sans GB","Microsoft YaHei",Monospace;}</style>
    </head>
    <body>
        <div id="windowSize">width=416/height=416</div>
        <div id="viewportContent">width=416,initial-scale=1</div>
        
        <script>
        var ele_html = document.querySelector('html');
        var ele_meta = document.querySelector('meta[name="viewport"]');
        if(ele_html.clientWidth < 416){
            ele_meta.content = 'width=416,initial-scale=' + (ele_html.clientWidth / 416);
        };
        
        document.getElementById('windowSize').textContent = 
        'width='+ele_html.clientWidth+'/height='+ele_html.clientHeight;
        document.getElementById('viewportContent').textContent = ele_meta.content;
        </script>
    </body>
</html>

↓ View & Code ↑

设置最小宽度可以让我们不用再担心设备屏幕小于设计稿而样式出错的问题,有个最小尺寸兜底总是好的,当然跟上面的例子一样,由于 resize 事件难以监听,所以尺寸变化后还是得手动按 F5 来刷新,但是 xj.viewport 插件也还是提供了对应的解决方案,照样是设置简单且能自动响应尺寸变化,在 meta 标签后引入插件,然后为 meta 标签添加 xj-viewport="{minWidth:416}" 属性就可以了。

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <!-- 在 meta 后引入 xj.viewport 插件,然后在 meta 设置 xj-viewport="{minWidth:416}" 即可 -->
        <meta name="viewport" xj-viewport="{minWidth:416}" content="width=device-width,initial-scale=1" />
        <script src="https://cdn.jsdelivr.net/gh/xjZone/xj.viewport@0.3.2/dist/xj.viewport.min.js"></script>
        <style>body{margin:0;font:20px/1.5 Menlo,Consolas,"Hiragino Sans GB","Microsoft YaHei",Monospace;}</style>
    </head>
    <body>
        <div id="windowSize">width=416/height=416</div>
        <div id="viewportContent">width=416,initial-scale=1</div>
        <script>
        var ele_windowSize = document.getElementById('windowSize');
        var ele_viewportContent = document.getElementById('viewportContent');
        
        window.addEventListener('resize', function(){
            ele_windowSize.textContent = 'width=' + xj.viewport.
            width() + '/height=' + xj.viewport.height();
            ele_viewportContent.textContent = xj.viewport.get();
        }, false);
        </script>
    </body>
</html>

↓ View & Code ↑

————

02.03. 配合 fullPage 定义窗口

在下面的例子中,我们来让 320 x 480 的内容总是能在窗口中完美显示,当窗口尺寸太小的时候就进行放大,当窗口尺寸太大的时候就进行缩小,这种设置有什么用?在做 fullPage 既 "整屏翻页" 的时候就很有用了,fullPage 是种每次滚动都会滚出整个屏幕距离,就类似于书籍翻页效果的玩意,这类项目在移动端如何进行窗口适配,一向是个难题,现在通过 viewport 设置我们能很好的解决它了。

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <style>
        html{overflow:hidden;width:100%;height:100%;color:#ffffff;}
        body{overflow:hidden;margin:0;width:100%;height:100%;background-color:#000000;}
        .pack{position:absolute;top:50%;left:50%;overflow:hidden;margin:-240px 0 0 -160px;width:320px;height:480px;}
        .page{position:relative;width:320px;height:480px;background-color:yellowgreen;}
        </style>
    </head>
    <body>
        <div class="pack">
            <div class="wrap">
                <div class="page">
                    <div id="windowSize"></div>
                    <div id="viewportContent"></div>
                </div>
            </div>
        </div>
        <script>
        (function(){
            
            // 获取当前窗口的宽高度,既 html 标签的尺寸
            var windowWidth = document.documentElement.clientWidth;
            var windowHeight = document.documentElement.clientHeight;
            
            // meta 标签的 content 属性可能需要用的变量
            var targetWidth = 'device-width';    // content 里 width 属性
            var targetInitialScale = 1;            // content 里 initial-scale 值
            var xScale = 1;                        // 横轴 initial-scale 值
            var yScale = 1;                        // 纵轴 initial-scale 值
            
            // 当前设备尺寸小于或大于目标尺寸计算比例值
            if(windowWidth < 320 || windowHeight < 480){
                if(windowWidth  < 320){ xScale = windowWidth  / 320 };
                if(windowHeight < 480){ yScale = windowHeight / 480 };
            }else 
            if(windowWidth > 320 && windowHeight > 480){
                if(windowWidth  > 320){ xScale = windowWidth  / 320 };
                if(windowHeight > 480){ yScale = windowHeight / 480 };
            };
            
            // 根据比例值算 width 和 initial-scale 属性
            if(xScale !== 1 || yScale !== 1){
                if(xScale <= yScale){ targetWidth = 320 }
                else{ targetWidth = Math.round(480 * windowWidth / windowHeight) };
                targetInitialScale = Math.min(xScale, yScale);
            };
            
            // 设置 meta[name="viewport"] 的 content 值
            var ele_meta = document.querySelector('meta[name="viewport"]');
            ele_meta.content = 'width='+ targetWidth +',initial-scale='+ targetInitialScale;
            
            // 写入当前窗口的尺寸以及当前视窗的设置结果
            var ele_html = document.querySelector('html');
            document.getElementById('windowSize').textContent = 
            'width='+ele_html.clientWidth+'/height='+ele_html.clientHeight;
            document.getElementById('viewportContent').textContent = ele_meta.content;
            
        })();
        </script>
    </body>
</html>

↓ View & Code ↑

在上面的例子中,经过视窗设置,能让 320 x 480 的内容被完整的显示出来,就是代码量有点多且在尺寸变化时还得手动刷新,改用 xj.viewport 会简单不少且无需手动刷新,在 meta 标签上添加 xj-viewport="{minWidth:320, minHeight:480, fillScreen:true, }" 即可,fillScreen 属性的意思是,如果窗口大于设置的最小宽高度,就将窗口缩小到符合设置的最小宽高度,也就是填充屏幕。

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1"
         xj-viewport="{minWidth:320, minHeight:480, fillScreen:true, }" />
        <script src="https://cdn.jsdelivr.net/gh/xjZone/xj.viewport@0.3.2/dist/xj.viewport.min.js"></script>
        <style>
        html{overflow:hidden;width:100%;height:100%;color:#ffffff;}
        body{overflow:hidden;margin:0;width:100%;height:100%;background-color:#000000;}
        .pack{position:absolute;top:50%;left:50%;overflow:hidden;margin:-240px 0 0 -160px;width:320px;height:480px;}
        .page{position:relative;width:320px;height:480px;background-color:yellowgreen;}
        </style>
    </head>
    <body>
        <div class="pack">
            <div class="wrap">
                <div class="page">
                    <div id="windowSize"></div>
                    <div id="viewportContent"></div>
                </div>
            </div>
        </div>
        <script>
        var ele_windowSize = document.getElementById('windowSize');
        var ele_viewportContent = document.getElementById('viewportContent');
        
        window.addEventListener('resize', function(){
            ele_windowSize.textContent = 'width=' + xj.viewport.
            width() + '/height=' + xj.viewport.height();
            ele_viewportContent.textContent = xj.viewport.get();
        }, false);
        </script>
    </body>
</html>

↓ View & Code ↑

上面两个 Demo 都只展示了窗口尺寸的设置,而没有真正的 fullPage 整屏翻页效果,下面我们展示一个完整的案例,业界中有不少插件如 swiper.jsfullpage.js 都可以实现整屏翻页的效果(后者是收费的请慎用),而下面这个 Demo 里的翻页效果是 XJ 自己写的,实际上也不复杂,就是代码有些长,这其实是 xj.viewport 插件的一个 Demo 案例,如果感兴趣的话可自行查看这个页面 : fullPage

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" 
         xj-viewport="{minWidth:400, minHeight:600, fillScreen:true, onlyMobile:false, }"/>
        <script src="https://cdn.jsdelivr.net/gh/xjZone/xj.viewport@0.3.2/dist/xj.viewport.min.js"></script>
        
        <style>
        /* ···
        代码太长就不继续贴了,有兴趣请自行查看 ↓
        https://xjZone.github.io/xj.viewport/page/demo_11_fullPage.html */

↓ View & Code ↑


03. 操作 viewport 可能遭遇的各种问题

以下内容是 XJ 在使用 viewport 时所发现的一些问题,涉及到了设置 viewport 时浏览器相关的 BUG,还有动态改变 viewport 时需要注意的点,以及对 fullPage 整屏翻页项目的尺寸设计建议,因为有些问题较难复现,所以 XJ 只是简单的使用文字描述,没有附上相关的 Demo,这部分内容较为琐碎且乏味,如果你实在懒得看,可以选择使用 xj.viewport 插件帮你设置的,这样就不用理会这些问题了。

————

03.01. 为什么不好定义 viewport 视窗的高度值

在第一章我们有提高窗口的高度可借助 width 属性和 initial-scale 属性得出,但那是不考虑地址栏和工具栏的理想化操作,实际的上由于它们的存在且可能会跟着页面的滚动而收起或放下,所以页面的高度变化可能会非常频繁,如果我们跟着变化而重新定义高度,就会让页面频繁缩放跳动,对此 XJ 的建议是,当高度缩小时才进行响应,也就是说窗口高度以地址栏和工具栏都存在的情况下为标准。

要设置窗口的尺寸,就得先得到当前窗口的尺寸值,但这一步在 Safari(IOS13-?) 中却并不简单,在 Safari(IOS) 横屏的情况下去使用 document.documentElement.clientHeight 属性,返回的窗口高度将包括顶部工具栏的尺寸,这其实是个 BUG,如果想得到不含工具栏的高度,得用 window.innerHeight,但 Safari(IOS) 的 window.innerHeightwindow.innerWidth 会受到视窗缩放的影响。

会受到视窗缩放的影响,就是这对属性在 viewport 被设置时会跟着变化(但 Android 这对属性则不受 viewport 设置的影响),为了在横屏时得到不含工具栏的正确高度,我们可以借助这对属性的比例再乘以 document.documentElement.clientWidth 属性,来计算出当前不含工具栏的窗口高度值,具体写法就是 window.innerHeight / window.innerWidth * document.documentElement.clientWidth

但是 window.innerHeight 属性在 Safari(IOS13-?) 中也是有 BUG 的,那就是如果在横屏的情况下进入页面,然后立即就获取这个属性,那么返回的值也会包括顶部的工具栏,也就是返回值会偏大,根本不准确,不过 Safari(IOS13-) 在横屏的情况下进入页面,还会很快的自动触发一次 resize 事件,在这个事件的回调中获取 window.innerHeight 属性,返回值就正常了,不会再包含工具栏高度。

上面提到的这两个高度 BUG,在 IOS14 似乎已经被修复了,而在 IOS13- 中并没有比较好的方法能解决这些问题,只能是监听 resize 事件然后重新获取 window.innerHeight 再进行尺寸计算而已,但截止 2021-06-18 2022-09-12,IOS14+ 市场占比已经到达 85% 99% 以上,所以这个浏览器的 BUG 其实不理会也无所谓啦,之后就等 IOS 系统更新迭代来自动解决这个问题吧,在这里就是做个记录而已。

————

03.02. 监听 resize 事件设置 viewport 的难点

在第二章我们有提到通过绑定 resize 事件来实现 viewport 的动态响应会十分麻烦,那是因为有些情况下触发了 resize 事件但我们却不能进行 viewport 设置,下面我们将说明这些情况,另外需要注意,由于 initial-scale 是以 device-width 为标准,所以每次在进行设置之前,得先初始化既 content="width=device-width,initial-scale=1",以获取到设备的原始理想尺寸再进行设置。

首先是在 Android 系统中,设置 meta[name="viewport"]content 属性可能触发 resize 事件,本来这个设置就可能是因为响应 resize 事件才被执行的,结果设置后又触发 resize 事件,这就会导致 resize 事件进入死循环,所以我们需要避免在这种情况下的响应,你可以在设置前定义一个变量,然后在 resize 事件的回调中检测到这个变量则直接 return 以避免进入这死循环。

其次是在 Android 系统中,小键盘的弹起也可能触发 resize 事件,但小键盘不该影响窗口布局的,也就是说此时也不该响应,那么我们就得去检测小键盘是否弹起了,但浏览器并没有提供小键盘相关的任何接口,XJ 在 xj.viewport 插件中是采用 screen.height - document.documentElement.clientHeight >= 240 来判断,意思是赌浏览器地址栏和工具栏的高度小于 240px 而小键盘大于 240px。

再者是页面滚动也可能触发 resize 事件,在部分浏览器如 Safari(IOS) 中滚动会导致地址栏和工具栏的收起或放下,此时 resize 事件就会频繁触发,但我们不该去响应的,因为会导致视窗频繁缩放跳动,且部分浏览器在视窗重置后,并不会将视窗的比例自动调整到最佳状态,需要用户手动去捏合缩小,但这操作可能导致再次触发 resize 事件,所以如果检测到只是高度变化,最好也是不要响应。

最后是屏幕翻转既 orientation 也会触发 resize 事件,这种情况当然是需要响应且进行 viewport 设置的,但响应就需要重新获取窗口的尺寸来进行计算,可有些浏览器在屏幕翻转后立即获取尺寸,就可能会得到翻转前的旧尺寸,也就是说尺寸的更新会存在延迟,这种情况下我们就得通过 setTimeout() 延迟 250500 毫秒再进行尺寸的获取和计算,以此解决这个尺寸更新不及时的问题。

————

03.03. 关于 fullPage 整屏翻页项目的一些补充

在上一章我们提到的 fullPage 项目,其实在中国它并没有明确的译名,一般大家会管它叫 "整屏翻页" 或 "整屏滚动" 或 "整页滑屏" 或 "全屏滚动",之前的 Demo 是探索如何为 fullPage 设置一个合适的窗口尺寸,因为这类玩意往往需要将所有内容塞在一个有限的页面容器中,但用户的屏幕尺寸五花八门,彼此之间区别不小,所以如何确保在不同设备上让页面保持相同的显示效果就成为了一个难题。

在这里我们讨论的 fullPage 整屏翻页项目,特指那些在移动端需要保持相同尺寸比例的项目,如果你是那种还得兼容 PC 端的响应式项目(如 fullpage.js 的官网),那么在移动端通过 viewport 设置来得到一个固定尺寸的做法(第二章第三节的案例)就不合适了,但此时你依然可以在移动端通过 viewport 设置来得到一个最小的窗口尺寸(第二章第二节的案例),以此解决窗口过小而内容不好放置的问题。

关于 fullPage 整屏翻页项目对尺寸定义的需求,实际上使用 viewport 来设置窗口尺寸并非唯一的解决方案,使用 rem 单位进行全局尺寸控制,或使用 transform:scale(n) 对界面进行缩放,都是可行的,但 rem 方案不好配合那些非 rem 单位的第三方插件,而 transform:scale(n) 方案则在页面内容和动画较多时容易引发页面崩溃,所以综合看上来,实际上还是 viewport 方案较为稳妥。

最后谈谈 fullPage 项目的 UI 设计,你可能发现之前的 Demo 都是使用 320*480(640*960) 或者 400*600(800*1200) 这种 2:3 的比例,而不是用 375*667(750*1334) 这种 9:16 的比例,那是因为浏览器还存在地址栏和工具栏,屏幕的高度在扣除掉地址栏和工具栏的高度后,其实尺寸比例更加接近于 2:3,所以这个比例下的尺寸会更合适,也许以后你可以建议你的设计师改一下设计稿?

设计师给我们尺寸为 640*960800*1200 的设计稿,我们则使用 320*480400*600 的尺寸去做页面,这涉及到设备像素比既 DPR 问题(在第一章已经提过了),XJ 比较推荐使用 400*600 的尺寸,因为现在通过 viewport 设置,窗口可大可小,不用再担心小屏设备的尺寸兼容问题了,其次是现在大屏设备较多,高一点的分辨率不容易让页面显得模糊,当然最终还得看设计师的意思啦。


参考内容

易企秀 - 整屏翻页参考

MDN - viewport 视口
MDN - viewport 属性列表
MDN - Viewport meta tag
MDN - window.visualViewport

Clancey - viewport-fit - 解决 iphoneX 的"刘海"问题
Quirk Smode - orientationchange 事件的浏览器兼容

MDN - 在非矩形屏幕环境下,设置边距的样式 env())
小火柴的蓝色理想 - 移动 web 开发之像素和 DPR

黑猫大侠LK - css 的那些事儿001 - 关于 initial-scale 的用法说明
leman314 - 详解 meta-viewport 中的 width 和 initial-scale 属性

Susie Kim - 在 100vh 布局中处理 iOS 的地址栏
Matt Smith - 移动 WebKit 中 100vh 的 CSS 修复

ralStyle贵 - 自适应的 fill-available/content 和 max/min-content
张鑫旭 - 理解 CSS3 的 max/min-content 及 fit-content 等 width 值

Alex Gibson - 关于移动端浏览器方向问题
David Walsh - 检测移动设备上的方向变化

XJ.Chen - xj.viewport


xjArea
12 声望0 粉丝