前文《浅谈挪动端单屏解决方案》中,我介绍了挪动端单屏场景下的一些惯例解决方案。然而,这些计划次要针对的是竖屏的页面,对于一种非凡状况:横屏单屏页面,咱们则须要进一步适配。

强制横屏

某些业务场景下,咱们的页面须要用户横屏能力进行浏览和操作 (横屏的网页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为其父元素。

横屏适配

前文总结的几种计划,只有稍作扭转,其实都能适配横屏

  1. 在横屏单屏页面中,咱们尽量应用vh进行布局:然而在safari中,100vh还包含了地址栏和工具栏的高度,导致理论的100vh比页面的可视区域要大,解决办法可参考《对于 Safari 100vh 的问题与解决方案》
  2. 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-contentwitdhheight写死,就能保障宽高比永远放弃为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: 强制横屏计划

同时这两个办法也反对Reacthooks写法: useDynamicRemuseForceLandscape

import { dynamicRem } from 'single-screen-utils';import { forceLandscape } from 'single-screen-utils';forceLandscape({  id: '#app',});dynamicRem({  pageWidth: 750,  pageHeight: 1334,});

更具体的应用办法,请看我的项目的README

参考

  • 前端 H5 横屏 独特解决计划详解
  • 横屏适配需要
  • H5游戏开发:横屏适配