强制横屏开发问题

lxf2023-05-05 13:02:01

前言

最近开发的横屏项目终于上线了,在开发中遇到了很多问题,最后都一一解决了。

需求来了

产品的需求明确是要强制横屏,这样微信和浏览器能保持一致,一屏展示,各个端都要保持一致,完美!

实际内心接手是非常抗拒,踩坑+看前人文章,遇到的问题层出不穷,竖屏能解决的,为啥要横屏呢?

产品的理由是,我看了王者荣耀,里面的活动都是横屏,应该很简单吧!微信和浏览器都支持一下!

横屏方案选型

强制横屏简单原理就是vw + translate(90deg)

视口单位vw转换和旋转90deg来解决强制横屏,这个要考虑滑动手势和一些附属弹窗的展示问题:

1.页面转90度后,滑动手势还是竖屏上下左右,导致页面滑动问题

2.页面滑动手势导致swiper滚动问题

3.页面拖拽元素和鼠标方向不一致问题

4.页面生成canvas分享图问题

5.ios刘海屏页面安全距离问题

6.页面tranform:scale后文字图片模糊问题

适配方案

  1. 首先横屏视口vw单位转换,通过postcss-px-to-viewport插件转换

2 配置landscape: true开启横屏转换,landscapeUnit转换横屏单位和landscapeWidth转换横屏宽度,这里根据页面设计配置,注意postcss-px-to-viewport版本一定要1.1.1及以上才支持横屏转换宽度

  1. 将body不设置转换vw,是为了后面生成分享图考虑
'postcss-px-to-viewport': {
      //把px单位转换为vw、vh、vmin或者vmax这样的视窗单位
      viewportWidth: 750, //视窗的宽度
      viewportHeight: 1334, //视窗的高度
      unitPrecision: 3, //将px转化为视窗单位值的小数位数
      viewportUnit: 'vw', //指定要转换成的视窗单位值
      selectorBlackList: ['.ignore', '.hairlines', 'van', 'body'], //指定不转换视窗单位值的类
      minPixelValue: 1, //小于等于1px不转换为视窗单位
      mediaQuery: false, //允许在媒体查询中使用px
      landscape: true,
      landscapeUnit: 'vw',
      landscapeWidth: 1334
    }

优点是:自动通过媒体查询将横屏后vw对应数值计算,这样不用手动去加工

  1. 然后页面旋转90deg判断,这个参考前人文章,监听页面旋转和resize
const reset = ()=>{
let width = document.documentElement.clientWidth,
            height = document.documentElement.clientHeight
          const h = Math.min(width, height)
          const w = Math.max(width, height)
          if (getOrientation() == 'portrait') {
           //竖屏状态
            el.style = `transform:rotate(90deg);transform-origin:${width / 2}px       center;width:${w}px;height:${h}px`
          } else if (getOrientation() == 'landscape') {
            //横屏状态
            el.style = `transform:rotate(0deg);transform-origin:${width /
              2}px center;width:${w}px;height:${h}px`
}

页面横屏适配了,剩下就是慢慢解决这些手势问题了!

问题解决方案,修复产品跳脚

1.首先是拖拽方向问题,相对竖屏的上下,横屏下,上下变成了左右,比较推荐vue-draggable-resizable组件,在组件内部将横屏方向的上下滑动距离转换为左右滑动距离,这样就简单了。

handleDrag (e) {
      const axis = this.axis
      const grid = this.grid
      const bounds = this.bounds
      const mouseClickPosition = this.mouseClickPosition

      const tmpDeltaX = axis && axis !== 'y' ? mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX) : 0
      const tmpDeltaY = axis && axis !== 'x' ? mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY) : 0


      const [deltaX, deltaY] = snapToGrid(grid, tmpDeltaX, tmpDeltaY, this.scale)

      // 修改鼠标计算方法
      const left = getOrientation() == 'portrait' ? restrictToBounds(mouseClickPosition.top - deltaY, bounds.minTop, bounds.maxTop) : restrictToBounds(mouseClickPosition.left - deltaX, bounds.minLeft, bounds.maxLeft)
      const top = getOrientation() == 'portrait' ? -restrictToBounds(mouseClickPosition.left - deltaX, bounds.minLeft, bounds.maxLeft) : restrictToBounds(mouseClickPosition.top - deltaY, bounds.minTop, bounds.maxTop)
}

2.页面滚动和swiper和拖拽一样的解决方法,在组件内部修改下判断上下距离的计算方法

3.canvas生成分享如果在旋转90deg的情况下,判断width和height非常麻烦,可以放置body下,页面结构如下:

<body>
/**分享dom**/
<div></div>
/**旋转dom**/
<div></div>
</body>

这样相当于在body竖屏下生成横屏的分享图,是不是图有点小?那就设置scale 2-3即可保证到横屏分享图的大小,清晰度非常好

  window.scroll(0, 0)
  const shareDom = document.getElementById('shareDom')
  html2canvas(document.getElementById('Share'), {
    width: shareDom.offsetWidth,
    height: shareDom.offsetHeight,
    scale: 3,
    useCORS: true
  }).then(canvas => {
     let src = canvas.toDataURL('image/png')
    // do something
  })

4.ios刘海屏安全距离,不然浏览器点不动,产品跳脚,横屏情况下,就加下padding-top为安全距离的right,constant和env函数是为了多兼容下ios的版本,css解决:

// 刘海屏安全距离
@supports (top: constant(safe-area-inset-right)) or
  (top: env(safe-area-inset-right)) {
  body .g-content {
    padding-top: constant(safe-area-inset-right);
    padding-top: env(safe-area-inset-right);
  }
}

5.页面不可避免的在多端上适配,有时tranform:scale后文字模糊了,就是有的异形端模糊了,那就改改scale

zoom:*
filter:blur(

基本的横屏遇到的几种场景都有了,插件拖本地修改后就那么放着,最后产品放行了,使用用户突破100w,暂时没发现啥问题。