前文《浅谈挪动端单屏解决方案》中,我介绍了挪动端单屏场景下的一些惯例解决方案。然而,这些计划次要针对的是竖屏的页面,对于一种非凡状况:横屏单屏页面,咱们则须要进一步适配。
强制横屏
某些业务场景下,咱们的页面须要用户横屏能力进行浏览和操作 (横屏的网页 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 游戏开发:横屏适配