前言
最近开发的横屏项目终于上线了,在开发中遇到了很多问题,最后都一一解决了。
需求来了
产品的需求明确是要强制横屏,这样微信和浏览器能保持一致,一屏展示,各个端都要保持一致,完美!
实际内心接手是非常抗拒,踩坑+看前人文章,遇到的问题层出不穷,竖屏能解决的,为啥要横屏呢?
产品的理由是,我看了王者荣耀,里面的活动都是横屏,应该很简单吧!微信和浏览器都支持一下!
横屏方案选型
强制横屏简单原理就是vw + translate(90deg)
视口单位vw转换和旋转90deg来解决强制横屏,这个要考虑滑动手势和一些附属弹窗的展示问题:
1.页面转90度后,滑动手势还是竖屏上下左右,导致页面滑动问题
2.页面滑动手势导致swiper滚动问题
3.页面拖拽元素和鼠标方向不一致问题
4.页面生成canvas分享图问题
5.ios刘海屏页面安全距离问题
6.页面tranform:scale后文字图片模糊问题
适配方案
- 首先横屏视口vw单位转换,通过postcss-px-to-viewport插件转换
2 配置landscape: true开启横屏转换,landscapeUnit转换横屏单位和landscapeWidth转换横屏宽度,这里根据页面设计配置,注意postcss-px-to-viewport版本一定要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对应数值计算,这样不用手动去加工
- 然后页面旋转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,暂时没发现啥问题。