前文《浅谈移动端单屏解决方案》中,我介绍了移动端单屏场景下的一些常规解决方案。但是,这些方案主要针对的是竖屏的页面,对于一种特殊情况:横屏单屏页面,我们则需要进一步适配。
强制横屏
某些业务场景下,我们的页面需要用户横屏才能进行浏览和操作 (横屏的网页H5游戏、内嵌在横屏手游中的webview活动页…)
常规且成本最低的解决方案是:当用户处于竖屏状态下浏览我们的网页时,我们只需要在页面上提醒用户手动切换为横屏即可。
但是对于一些锁定屏幕方向的用户来说,还需要解除竖屏才能访问我们的页面,用户体验上来说的确大打折扣。
因此,理想状态下,我们更希望的交互是:当用户横屏状态时,网页正常访问;当用户竖屏状态时,展示内容强制横屏。
解决方法如下: 我们将最外层的容器元素(#app
),在竖屏时强行旋转90度即可
landscape
landscape
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
优化版的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 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 Math.abs(dX) && dY > 0) {
console.log('上往下滑');
// 强制横屏(本身是竖屏)的情况下,监听上下滑动
if (!isLandscape) {
swiper.slideNext();
}
} else if (Math.abs(dY) > Math.abs(dX) && dY
手机竖屏打开此页面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游戏开发:横屏适配
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net