乐趣区

关于前端:浅谈移动端单屏解决方案

前文《挪动端常见适配计划》中,我介绍了挪动端的一些常见适配计划

挪动端适配就是在进行 屏幕宽度 的等比例缩放

文中我强调了挪动端适配是对 屏幕宽度 进行缩放:对于一般的流式布局(长屏幕页面),页面内容是能够高低滚动的。屏幕小,一屏幕看到的货色尽管变少,然而用户能够通过手势滚动页面,持续浏览下一屏的内容。因而在惯例状况下,对于屏幕宽度进行等比例缩放曾经能解决大部分利用场景了

然而对于一种非凡的场景:单屏页面(又称翻屏页面),因为须要把 一整屏 的内容 残缺 展现给用户,同时又要求页面不能呈现滚动条,那么,仅仅只是针对屏幕宽度进行等比例缩放的适配,其实成果并不现实

设施独立像素 (CSS 像素)

设施独立像素是一种能够被程序所管制的虚构像素,在 Web 开发中对应 CSS 像素

iphone7 举例:

iphone7 设施独立像素 375 * 667

也就是手机全屏下的大小,同时也是 chrome 模拟器展现的尺寸

能够通过 js 的 screen.widthscreen.height获取

设施像素 (物理像素)

设施像素也能够叫物理像素,由设施的屏幕决定,其实就是屏幕中管制显示的最小单位

iphone7 举例:

iphone7 设施像素 750 * 1334

750 * 1334这个尺寸也能够称为 设计像素,咱们设计和开发页面时,就是以这个设计像素为准

设施像素比(DPR)

设施像素比(dpr) = 设施像素 / 设施独立像素

iphone7 举例:

iphone7dpr = 2 = 750 / 375

也就是说,在 iphone7 下,1 css 像素 = 2 物理像素

css 中一个 1x1 大小的正方形外面,其实有 4 个物理像素

dpr大于 2 的屏幕也称为视网膜屏幕(Retina)

理论物理像素

iphone7的理论物理像素是 750 * 1334,刚好等于 设施像素 。但不是所有的设施都是 理论物理像素 等于 设施像素

iphone7 plus的理论物理像素是 1080 * 1920。它的dpr 为 3,设施独立像素 414 * 736,依据公式能够得出,它的 设施像素 等于 1242 x 2208,远大于理论物理像素。手机会主动把1242 * 2208 个像素点塞进 1080 * 1920 个物理像素点来渲染,咱们不必关怀这个过程

单屏幕

后面介绍了这么多概念,其实在真正开发中,咱们次要关怀的是 设施独立像素 设施像素

设施像素 决定了设计稿的尺寸。挪动端设计稿个别是750 * 1334 尺寸大小(iPhone6 的设施像素为规范的设计图),因而绝对比拟固定

设施独立像素 决定了设施的屏幕大小。iOS 平台下,屏幕尺寸还算绝对固定,然而到了 Android 平台下,屏幕尺寸那就 千奇百怪,百花争鸣 了。

特地须要留神的一点:即便 设施独立像素 确定了大小,咱们的网页被用户看到的时候,理论高度还是比 设施独立像素 的高度小很多

次要起因是:咱们的网页往往是在手机的浏览器上拜访的,而这些浏览器自带了顶部地址栏和底部工具栏,这两局部的高度又进一步压缩了咱们网页展现的高度(如果咱们的网页是在第三方客户端内关上的,比方微博,微信,Twitter, Facebook,那么个别只有顶部地址栏)

举个例子,iphone11的设施独立像素是414 * 896

第一张图是在 safari 浏览器下:能够看到高低红框局部是浏览器自带的区域,只有蓝框是理论网页展现的高度,这个蓝框的大小是 414 * 715(documentElement.clientWidth/documentElement.clientHeight),曾经比设施独立像素的高度少了 181 像素(896 – 715)

第二张图是在 微信 自带浏览器下:能够看到顶部红框局部是浏览器自带的区域,只有蓝框是理论网页展现的高度,这个蓝框的大小是 414 * 804(documentElement.clientWidth/documentElement.clientHeight),也比设施独立像素的高度少了 92 像素(896 – 804)

收集到的一些常见设施尺寸大小:

品牌 操作系统 设施 设施独立像素 (screen.width/screen.height) 自带浏览器下(clientWidth/clientHeight)
苹果 iOS iPhone 7 375 * 667 375 * 548
iPhone 12 390 * 844 390 * 664
Ipnone 11/XR 414 * 896 414 * 715
iPhone X 375 * 812 375 * 635
华为 安卓 P40 360 * 780 360 * 625
nova 8 SE 360 * 800 360 * 659
Mate 30 424 * 918 424 * 774
光荣 8 360 * 640 360 * 501
P10 360 * 640 360 * 526
畅玩 7x 360 * 720 360 * 584
Oppo 安卓 R15x 360 * 780 360 * 650
R17 360 * 780 360 * 628
K1 360 * 780 360 * 622
Xiaomi 安卓 MIX 2 393 * 786 393 * 666
小米 10 393 * 851 393 * 720
小米 6 360 * 640 360 * 521
k40 393 * 873 393 * 713

单屏难点

设计稿的的宽高比是固定的,然而实在设施的宽高比永远不是对立的,并且网页的可视区域还会随着拜访形式 (浏览器,APP 客户端) 有所扭转。

同一份设计稿,却要在不同尺寸的设施上,都展现出良好的布局:页面的内容要尽可能 残缺 展现在 一屏 之中(甚至不能有滚动条)

安全区 + 长背景图

750 * 1334 的设计稿,须要思考到长屏幕时,页面的展现状况

比方默认 750 * 1334 大小的内容须要残缺展现进去 (安全区),长屏幕750 * 1750 时,把安全区的内容垂直居中展现即可

此时,咱们就须要应用一张长背景图 (750 * 1750) 高低居中作为整个网页的背景

<style>
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }

  html {
    /* 750px 的设计图,1rem = 100px */
    font-size: calc(100 * 100vw / 750);
  }

  html,
  body {
    width: 100%;
    height: 100%;
    position: relative;
  }

  #app {
    width: 100%;
    height: 100%;
    position: relative;
    /* 长背景图高低居中 */
    background: url('./img/bg.jpg') no-repeat center / cover #fff;
    overflow: hidden;
  }

  .safe-content {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
     /* 限定安全区的高度 */
    max-height: 13.34rem;
    top: 50%;
    /* 安全区高低居中 */
    transform: translateY(-50%);
  }
</style>
<body>
  <div id='app'>
    <div class='safe-content'>
      <div class='block1'></div>
      <div class='block2'></div>
    </div>
  </div>
</body>

之后,咱们把页面所有的内容,绝对 safe-content 进行布局

残缺页面,点击此处预览 (手机模式查看)

残缺代码,间接右键源代码

<!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>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    html {
      /* 750px 的设计图,1rem = 100px */
      font-size: calc(100 * 100vw / 750);
    }

    html,
    body {
      width: 100%;
      height: 100%;
      position: relative;
    }

    #app {
      width: 100%;
      height: 100%;
      position: relative;
      background: url('./img/bg.jpg') no-repeat center / cover #fff;
      overflow: hidden;
    }

    .safe-content {
      width: 100%;
      height: 100%;
      position: absolute;
      left: 0;
      max-height: 13.34rem;
      top: 50%;
      transform: translateY(-50%);
      /* border: 1px solid red; */
    }

    .go-back {
      width: 0.91rem;
      height: 0.92rem;
      background: url('./img/back.png') no-repeat center / contain;
      position: absolute;
      left: 0.14rem;
      top: 0.15rem;
    }

    .rule-btn {
      width: 0.83rem;
      height: 0.83rem;
      background: url('./img/rule-btn.png') no-repeat center / contain;
      position: absolute;
      left: 1.27rem;
      top: 0.20rem;
    }

    .username {
      min-width: 1.67rem;
      height: 0.41rem;
      line-height: 0.41rem;
      background: url('./img/user-bg.png') no-repeat center / cover;
      position: absolute;
      right: 0;
      top: 0.25rem;
      color: #fcfcfc;
      font-size: 0.22rem;
      text-align: right;
      padding-right: 0.13rem;
      padding-left: 0.48rem;
    }

    .gift-container {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      display: flex;
      flex-direction: column;
      align-items: center;
    }

    .gift-logo {
      width: 6.79rem;
      height: 7.03rem;
      background: url('./img/game-title.png') no-repeat center / contain;
    }

    .gift-title {
      width: 3.45rem;
      height: 0.63rem;
      background: url('./img/congratulate.png') no-repeat center / contain;
    }

    .gift-content {
      margin-top: -1.8rem;
      display: flex;
      flex-direction: column;
      align-items: center;
    }

    .gift-name {
      font-size: 0.3216rem;
      line-height: 0.24rem;
      letter-spacing: 0.032rem;
      color: #bd5874;
      text-align: center;
      margin-top: 0.2rem;
      text-shadow: 0 0 5px #fff, 0 0 5px #fff;
    }

    .gift-icon {
      margin: 0.4rem 0 0.5rem;
      width: 1.94rem;
      height: 1.65rem;
      background: url('./img/gift-1.png') no-repeat center / contain;
    }

    .gift-desc {
      font-size: 0.2rem;
      line-height: 0.2426rem;
      letter-spacing: 0.019rem;
      color: #90949e;
      text-align: center;
      margin-bottom: 0.2rem;
      text-shadow: 0 0 5px #fff, 0 0 5px #fff;
    }

    .gift-get-info {
      font-size: 0.23rem;
      line-height: 0.30rem;
      color: #d16b88;
      text-align: center;
      text-shadow: 0 0 5px #fff, 0 0 5px #fff;
    }
  </style>
</head>

<body>
  <div id='app'>
    <div class="safe-content">
      <div class="go-back"></div>
      <div class="rule-btn"></div>
      <div class="username"> 深红 </div>
      <div class="gift-container">
        <div class="gift-logo"></div>
        <div class="gift-content">
          <div class="gift-title"></div>
          <p class="gift-name"> 水漾烛光礼盒 </p>
          <div class="gift-icon"></div>
          <div class="gift-desc">
            <p> 蒂普提克香氛蜡烛(70g)*1</p>
            <p>krramel 沐浴套装 *1</p>
          </div>
          <div class="gift-get-info">
            <p> 请到游戏内【精彩流动 - 实物周边处分兑换】</p>
            <p> 填写支付信息 </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>

</html>

宽高比

下面的计划,对于挪动端 ( 屏幕高度大于屏幕宽度 ) 的大部分场景,确实够用了。

然而在折叠屏手机 ( 屏幕宽度和高度差异不大 ),ipad,pc 端( 屏幕高度小于屏幕宽度 ) 的设施下,咱们的页面就很有可能超出了残缺的一屏。

如果此时,父级元素还设置了overflow: hidden;,那么用户甚至不能滑动查看超出屏幕的内容,如果底部是一个可交互的按钮,那么用户就永远不能触发之后的流程了!

问题起因

咱们的 rem 适配计划,是绝对于屏幕宽度进行缩放的,然而不同机型的手机,可视区域的宽高比并不固定,因而对于局部手机,页面内容就很有可能呈现 超出屏幕底部 或者 底部留有空白

对于 底部留有空白 ,个别产生在可视高度比可视宽度大很多的状况,后面介绍的 安全区 + 长背景图 计划,就是针对此种状况的解决方案。

而对于 超出屏幕底部 ,个别产生在可视高度和可视宽度相差不大(折叠屏手机),甚至可视高度比可视宽度小(横屏或者 pc 端) 的状况,解决方案个别如下:

  • 应用 css 进行宽高比判断
  • 应用 js 进行宽高比判断
  • 应用 js 动静批改 rem 大小
  • 应用 js 动静缩放整体页面
  • 应用 vwvh进行布局

aspect-ratio

留神和 device-aspect-ratio 进行辨别,device-aspect-ratio是和设施尺寸进行绑定的,然而咱们之前介绍过:网页的可视区域会随着拜访形式 (浏览器,APP 客户端) 有所扭转,因而 aspect-ratio 才是咱们真正须要的属性。

aspect-ratio 定义输出设备中的页面可见区域宽度与高度的比率

同时它有两个 max-aspect-ratiomin-aspect-ratio兄弟属性,能够和 max-widthmin-width进行类比:

@media screen and (min-aspect-ratio: 9/16) {// 只有宽高比大于等于 9 /16,就会执行}

@media screen and (min-aspect-ratio: 3/4) {// 只有宽高比大于等于 3 /4,就会执行}

@media screen and (min-aspect-ratio: 1/1) {// 只有宽高比大于等于 1 /1,就会执行}

对于下面的页面,咱们失常的设施独立像素是375 * 667,咱们能够这样进行高度划分:

  • 高度大于 667:无需调整,咱们只怕高度小,不怕高度大,高度大时曾经有后面的计划: 安全区 + 长背景图
  • 530-667:还是失常,咱们也不须要调整
  • 490-530
  • 375-490
  • 小于 375:pc 端或者横屏,高度曾经比宽度小了
@media screen and (min-aspect-ratio: 375 / 530) {
  .safe-content {transform: translateY(-50%) scale(0.8);
  }
}

@media screen and (min-aspect-ratio: 375 / 490) {
  .gift-logo {transform: scale(0.6);
  }
}

@media screen and (min-aspect-ratio: 375 / 375) {
  .safe-content {transform: translateY(-50%) scale(0.32);
  }
  .gift-logo {transform: scale(0.4);
  }
}

比较简单暴力的,就是间接对次要页面区域施加 transform: scale(0.8) 这类款式,间接放大。

至于我刚刚划分的高度区间,是通过 chrome 模拟器 本人一次次试验调整进去的,这个要 具体页面具体分析,并没有一个对立的宽高比规定。

残缺页面,点击此处 aspect-ratio- 预览 (手机模式查看)

残缺代码,间接右键源代码

JS 增加全局类

function detectAspectRatio(aspectRatio) {document.documentElement.classList.remove('pc', 'mobile1', 'mobile2');
  if (aspectRatio >= 375 / 375) {return document.documentElement.classList.add('pc');
  }

  if (aspectRatio >= 375 / 490) {return document.documentElement.classList.add('mobile1');
  }
  if (aspectRatio >= 375 / 530) {return document.documentElement.classList.add('mobile2');
  }
}
function init() {
  const clientWidth = document.documentElement.clientWidth;
  const clientHeight = document.documentElement.clientHeight;
  const aspectRatio = clientWidth / clientHeight;
  detectAspectRatio(aspectRatio);
}
init();
window.addEventListener('resize', init);

实质上就是把 css 的媒体查问 aspect-ratio 用 js 实现了一遍,所以这种计划区别不大。

动静批改 rem

默认状况下,咱们的 rem 是依据可视区域宽度进行计算的,然而在高度较小的状况下,咱们能够动静的批改 rem 的参考对象,让它依据可视高度进行计算

咱们甚至能够实现:无论窗口怎么变,咱们的内容都放弃原来的比例,并尽量占满窗口

封装成一个通用 flexible.js 办法

const defaultConfig = {
  pageWidth: 750,
  pageHeight: 1334,
  pageFontSize: 32,
};

const flexible = (config = defaultConfig) => {
  const {
    pageWidth = defaultConfig.pageWidth,
    pageHeight = defaultConfig.pageHeight,
    pageFontSize = defaultConfig.pageFontSize,
  } = config;
  const pageAspectRatio = defaultConfig.pageAspectRatio || (pageWidth / pageHeight);
  // 依据屏幕大小及 dpi 调整缩放和大小
  function onResize() {
    let clientWidth = document.documentElement.clientWidth;
    let clientHeight = document.documentElement.clientHeight;
    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);
    }

    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`;
      }
    }
  };
};

应用时:

flexible({pageFontSize: 100});
.safe-content {
  width: 7.5rem;
  height: 13.34rem;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
}

这时,只须要把 safe-contentwitdhheight 写死,就能保障宽高比永远放弃为750 * 1334

残缺页面,点击此处动静批改 rem- 预览 (手机模式查看)

扭转屏幕大小,能够看到 safe-content 始终都放弃失常的宽高比,并且总是宽度占满 (宽度比高度小) 或者高度占满(高度比宽度小)

残缺代码,间接右键源代码

另外一个简化版本

(function init(screenRatioByDesign = 750 / 1334) {
  let docEle = document.documentElement
  function setHtmlFontSize() {
    var screenRatio = docEle.clientWidth / docEle.clientHeight;
    // 7.5 = 750 / 100  (100 是设计稿上的 1rem 大小)
    var fontSize = (
      screenRatio > screenRatioByDesign
        ? (screenRatioByDesign / screenRatio)
        : 1
    ) * docEle.clientWidth / 7.5;
    docEle.style.fontSize = fontSize.toFixed(3) + "px";
  }

  setHtmlFontSize()

  window.addEventListener('resize', setHtmlFontSize)
})()

残缺页面,点击此处动静批改 rem2- 预览 (手机模式查看)

动静缩放整体页面

后面的适配计划,咱们都借助了 rem 进行页面的大小缩放。其实咱们也能够间接应用 px 进行页面的布局,最初对立应用 js 进行整体缩放

 function init() {
    const winWidth = document.documentElement.clientWidth;
    const winHeight = document.documentElement.clientHeight;
    const winScale = winWidth / winHeight;

    const page = document.querySelector('.safe-content');
    const pageWidth = 750;
    const pageHeight = 1334;
    const pageScale = pageWidth / pageHeight;

    const origin = '50% 50% 0';

    let initialScale = 1;

    if (winScale > pageScale) {
      // 宽度长了,然而高度不够
      // 高度占满,宽度程度居中
      console.log('高度占满,宽度程度居中');
      initialScale = winHeight / pageHeight;
    } else {console.log('宽度占满,高度垂直居中');
      // 高度长了,然而宽度不够
      // 宽度占满,高度垂直居中
      initialScale = winWidth / pageWidth;
    }

    page.style.width = pageWidth + 'px';
    page.style.height = pageHeight + 'px';
    page.style.transform = 'scale(' + initialScale + ')';
    page.style.transformOrigin = origin;
    page.style.left = (pageWidth - winWidth) / -2 + 'px';
    page.style.top = (pageHeight - winHeight) / -2 + 'px';
  }
  init();
  window.addEventListener('resize', init);
.safe-content {
  /* 布局间接写死成设计稿上的大小 */
  width: 750px;
  height: 1334px;
  position: absolute;
  left: 0;
  top: 0;
  border: 1px solid red;
}

.gift-logo {
  /* 布局间接写死成设计稿上的大小 */
  width: 679px;
  height: 703px;
  background: url('./img/game-title.png') no-repeat center / contain;
}

这时,只须要把 safe-contentwitdhheight 写死,就能保障宽高比永远放弃为750 * 1334

残缺页面,点击此处动静缩放整体页面 - 预览 (手机模式查看)

扭转屏幕大小,能够看到 safe-content 始终都放弃失常的宽高比,并且总是宽度占满 (宽度比高度小) 或者高度占满(高度比宽度小)

残缺代码,间接右键源代码

vw 和 vh

单屏页面布局时,垂直定位尽可能相对高度进行定位,这时能够抉择应用百分比或者vh

@use 'sass:math';

@function px2vh($px, $height: 1334) {@return math.div($px, $height) * 100 * 1vh;
}

能够封装一个 scss 办法,将测量失去的 px 转换成vh

.demo {
  position: absolute;
  left: 0;
  top: 25%; // 垂直定位单位为 %
  top: px2vh(100); // 垂直定位单位为 vh
  width: 100%;
  height: 1rem;
}

如果心愿页面的元素在不同的高度下,均能残缺展现,能够全副应用 vh 进行布局

.gift-title {
  width: 25.86vh;
  height: 4.72vh;
  background: url('./img/congratulate.png') no-repeat center / contain;
}

.gift-name {font-size: 2.39vh;}

残缺页面,点击此处 vh- 预览 (手机模式查看)

残缺代码,间接右键源代码

总结

后面介绍的 5 种适配计划,能够总结如下:

  1. 应用 vwvh这两个原生的 css3 单位,人造反对宽度和高度的适配:对于须要高度适配的元素,应用vh,对于须要宽度适配的元素,应用vw
  2. rem绝对宽度计算:划分几个高度区域,对于特定的宽高比,独自进行适配。整体页面还是绝对宽度进行缩放,只针对局部宽高比,对页面进行特定的款式改变
  3. rem动静扭转:即可绝对宽度,也可相对高度进行计算,此种计划,能够做到 放弃设计稿的宽高比例,并尽量占满窗口 的极致成果
  4. 不应用 rem,写死px,间接js 整体缩放页面:此种计划,也能够做到 放弃设计稿的宽高比例,并尽量占满窗口 的极致成果

对于 放弃设计稿的宽高比例,并尽量占满窗口 的成果,能够点击上面的 demo 进行预览了解:

调整屏幕大小,能够看见页面会上下居中或者左右居中,并且放弃宽高比

  • 放弃 16:9 的宽高比 -rem 动静扭转
  • 放弃 16:9 的宽高比 -rem 动静扭转 2
  • 放弃 16:9 的宽高比 - 整体缩放

    参考

  • 对于挪动端适配,你必须要晓得的
  • 不要再问我挪动适配的问题了
  • device-aspect-ratio 与 aspect-ratio 单屏布局
  • 翻页 H5 全分辨率适配最佳实际
  • 大屏上的全屏页面的自适应适配计划
  • 挪动端单屏解决方案
  • 挪动端单屏解决方案(续)
  • 如何打造一个高效适配的 H5
  • 全屏 HTML5 适配个人见解
  • 通过 rem 布局 +media-query:aspect-ratio 实现挪动端全机型适配笼罩
退出移动版