前文《浅谈挪动端单屏解决方案》中,我介绍了挪动端单屏场景下的一些惯例解决方案。然而,这些计划次要针对的是竖屏的页面,对于一种非凡状况:横屏单屏页面,咱们则须要进一步适配。
强制横屏
某些业务场景下,咱们的页面须要用户横屏能力进行浏览和操作 (横屏的网页H5游戏、内嵌在横屏手游中的webview流动页...)
惯例且老本最低的解决方案是:当用户处于竖屏状态下浏览咱们的网页时,咱们只须要在页面上揭示用户手动切换为横屏即可。
然而对于一些锁定屏幕方向的用户来说,还须要解除竖屏能力拜访咱们的页面,用户体验上来说确实大打折扣。
因而,现实状态下,咱们更心愿的交互是:当用户横屏状态时,网页失常拜访;当用户竖屏状态时,展现内容强制横屏。
解决办法如下: 咱们将最外层的容器元素(#app
),在竖屏时强行旋转90度即可
<!DOCTYPE html><html lang='en'><head> <meta charset='UTF-8'> <meta http-equiv='X-UA-Compatible' content='IE=edge'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>landscape</title> <style> * { margin: 0; padding: 0; } html, body { width: 100%; height: 100%; } #app { width: 100%; height: 100%; background: orange; position: absolute; } </style></head><body> <div id='app'> <p>landscape</p> </div></body></html>
forceLandscape.js
const forceLandscape = (id = '#app') => { const handler = () => { let width = document.documentElement.clientWidth; let height = document.documentElement.clientHeight; let targetDom = document.querySelector(id); if (!targetDom) return; // 如果宽度比高度大,则认为处于横屏状态 // 也能够获取 window.orientation 方向来判断屏幕状态 if (width > height) { targetDom.style.position = 'absolute'; targetDom.style.width = `${width}px`; targetDom.style.height = `${height}px`; targetDom.style.left = `${0}px`; targetDom.style.top = `${0}px`; targetDom.style.transform = 'none'; targetDom.style.transformOrigin = '50% 50%'; } else { targetDom.style.position = 'absolute'; targetDom.style.width = `${height}px`; targetDom.style.height = `${width}px`; targetDom.style.left = `${0 - (height - width) / 2}px`; targetDom.style.top = `${(height - width) / 2}px`; targetDom.style.transform = 'rotate(90deg)'; targetDom.style.transformOrigin = '50% 50%'; } }; const handleResize = () => { setTimeout(() => { handler(); }, 300); }; window.addEventListener('resize', handleResize); handler();};
点击页面 强制竖屏 查看成果
顶层组件
下面的计划,咱们只是将最外层的容器元素(#app
)进行了90度的旋转,如果一些顶层组件,例如Modal
弹窗,它们个别是渲染在#app
的外层(避免层级笼罩),强制横屏时,这些UI组件依然是竖屏款式。
Ant Design Mobile的Modal组件就提供了一个getContainer
的属性,能够自定义Modal
的父级,这时候咱们就能够指定#app
为其父元素。
横屏适配
前文总结的几种计划,只有稍作扭转,其实都能适配横屏
- 在横屏单屏页面中,咱们尽量应用
vh
进行布局:然而在safari
中,100vh
还包含了地址栏和工具栏的高度,导致理论的100vh
比页面的可视区域要大,解决办法可参考《对于 Safari 100vh 的问题与解决方案》 - rem动静扭转:仍旧应用前文的动静批改rem的计划,只不过须要留神批改一处中央
function onResize() { let clientWidth = document.documentElement.clientWidth; let clientHeight = document.documentElement.clientHeight; // 该页面须要强制横屏,强制横屏时,替换两者的值 if (clientWidth < clientHeight) { [clientWidth, clientHeight] = [clientHeight, clientWidth]; } // 省略其余代码}
优化版的flexible.js
,反对横屏模式和竖屏模式
const defaultConfig = { pageWidth: 750, pageHeight: 1334, pageFontSize: 32, mode: 'portrait', // 默认竖屏模式};const flexible = (config = defaultConfig) => { const { pageWidth = defaultConfig.pageWidth, pageHeight = defaultConfig.pageHeight, pageFontSize = defaultConfig.pageFontSize, mode = defaultConfig.mode, } = config; const pageAspectRatio = defaultConfig.pageAspectRatio || (pageWidth / pageHeight); // 依据屏幕大小及dpi调整缩放和大小 function onResize() { let clientWidth = document.documentElement.clientWidth; let clientHeight = document.documentElement.clientHeight; // 该页面须要强制横屏 if (mode === 'landscape') { if (clientWidth < clientHeight) { [clientWidth, clientHeight] = [clientHeight, clientWidth]; } } let aspectRatio = clientWidth / clientHeight; // 根元素字体 let e = 16; if (clientWidth > pageWidth) { // 认为是ipad/pc console.log('认为是ipad/pc'); e = pageFontSize * (clientHeight / pageHeight); } else if (aspectRatio > pageAspectRatio) { // 宽屏挪动端 console.log('宽屏挪动端'); e = pageFontSize * (clientHeight / pageHeight); } else { // 失常挪动端 console.log('失常挪动端'); e = pageFontSize * (clientWidth / pageWidth); } e = parseFloat(e.toFixed(3)); document.documentElement.style.fontSize = `${e}px`; let realitySize = parseFloat(window.getComputedStyle(document.documentElement).fontSize); if (e !== realitySize) { e = e * e / realitySize; document.documentElement.style.fontSize = `${e}px`; } } const handleResize = () => { onResize(); }; window.addEventListener('resize', handleResize); onResize(); return (defaultSize) => { window.removeEventListener('resize', handleResize); if (defaultSize) { if (typeof defaultSize === 'string') { document.documentElement.style.fontSize = defaultSize; } else if (typeof defaultSize === 'number') { document.documentElement.style.fontSize = `${defaultSize}px`; } } };};
应用时:
// 调用此办法前,须要先调用forceLandscape强制横屏forceLandscape();flexible({ pageWidth: 1334, pageHeight: 750, pageFontSize: 100, mode: 'landscape', // 横屏模式});
.safe-content { width: 13.34rem; height: 7.5rem; position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto;}
这时,只须要把safe-content
的witdh
和height
写死,就能保障宽高比永远放弃为1334 * 750
残缺页面,点击此处动静批改rem-预览 (反对pc和挪动端)
横屏交互
在竖屏状态时,尽管咱们的页面被强制旋转90度,实现了强制横屏,但此时页面的交互手势,仍是竖屏状态:最显著的就是轮播图swiper
组件
手机竖屏关上此页面swiper-预览能够发现,当页面处于强制横屏(手机依然是竖屏)时,咱们须要左右滑动,能力高低切换轮播图
解决方案是:咱们敞开swiper
默认的监听手势,由咱们本人来实现监听滑动。当处于失常横屏时,咱们监听左右滑动;当处于强制横屏(手机依然是竖屏)时,咱们监听高低滑动。
敞开默认的手势监听
const swiper = new Swiper('.swiper', { allowTouchMove: false, // 敞开主动监听触摸});
自定义监听手势方向
let startX = 0;let startY = 0;let isLandscape = false;const handler = () => { let width = document.documentElement.clientWidth; let height = document.documentElement.clientHeight; if (width > height) { isLandscape = true; } else { isLandscape = false; }};handler();window.addEventListener('resize', handler);const handleTouchstart = (e) => { startX = e.touches[0].pageX; startY = e.touches[0].pageY;};const handleTouchend = (e) => { let endX; let endY; endX = e.changedTouches[0].pageX; endY = e.changedTouches[0].pageY; const dX = endX - startX; const dY = endY - startY; if (Math.abs(dX) > Math.abs(dY) && dX > 0) { console.log('左往右滑'); // 横屏的状况下,监听左右滑动 if (isLandscape) { swiper.slideNext(); } } else if (Math.abs(dX) > Math.abs(dY) && dX < 0) { console.log('右往左滑'); // 横屏的状况下,监听左右滑动 if (isLandscape) { swiper.slidePrev(); } } else if (Math.abs(dY) > Math.abs(dX) && dY > 0) { console.log('上往下滑'); // 强制横屏(自身是竖屏)的状况下,监听高低滑动 if (!isLandscape) { swiper.slideNext(); } } else if (Math.abs(dY) > Math.abs(dX) && dY < 0) { console.log('下往上滑'); // 强制横屏(自身是竖屏)的状况下,监听高低滑动 if (!isLandscape) { swiper.slidePrev(); } } else { console.log('滑了个寂寞'); }};const dom = document.querySelector('.safe-content');dom.addEventListener('touchstart', handleTouchstart);dom.addEventListener('touchend', handleTouchend);
手机竖屏关上此页面swiper自定义手势-预览,能够发现这次的交互手势是失常的。
单屏工具办法
依据后面总结的一系列单屏布局技巧(竖屏、横屏),我封装了两个罕用的工具办法,已公布到npm
上
single-screen-utils
dynamicRem
: 动静设置rem
forceLandscape
: 强制横屏计划
同时这两个办法也反对React
的hooks
写法: useDynamicRem
和useForceLandscape
import { dynamicRem } from 'single-screen-utils';import { forceLandscape } from 'single-screen-utils';forceLandscape({ id: '#app',});dynamicRem({ pageWidth: 750, pageHeight: 1334,});
更具体的应用办法,请看我的项目的README
参考
- 前端 H5 横屏 独特解决计划详解
- 横屏适配需要
- H5游戏开发:横屏适配