下面列举了我遇到的一些常规问题,若有遇到其余问题请在评论区补充,以后我也会实践后加以补充,感谢!css
以前发过两篇文章已经详细的论述了1px 问题与 移动端适配,并给出了原理和方案。html
接下来呢,咱们看看其余问题的原理和解决方案吧。前端
上下滑动页面会产生卡顿,手指离开页面,页面当即中止运动。总体表现就是滑动不流畅,没有滑动惯性。vue
为何 iOS 的 webview 中 滑动不流畅,它是如何定义的?程序员
最终我在 safari
文档里面寻找到了答案(文档连接在参考资料项)。web
原来在 iOS 5.0 以及以后的版本,滑动有定义有两个值 auto
和 touch
,默认值为 auto
。npm
-webkit-overflow-scrolling: touch; /* 当手指从触摸屏上移开,会保持一段时间的滚动 */-webkit-overflow-scrolling: auto; /* 当手指从触摸屏上移开,滚动会当即中止 */复制代码
将-webkit-overflow-scrolling
值设置为 touch
canvas
.wrapper { -webkit-overflow-scrolling: touch;}复制代码
设置滚动条隐藏:
.container ::-webkit-scrollbar {display: none;}
后端
可能会致使使用position:fixed;
固定定位的元素,随着页面一块儿滚动浏览器
设置外部 overflow
为 hidden
,设置内容元素 overflow
为 auto
。内部元素超出 body 即产生滚动,超出的部分 body 隐藏。
body { overflow-y: hidden;}.wrapper { overflow-y: auto;}复制代码
二者结合使用更佳!
手指按住屏幕下拉,屏幕顶部会多出一块白色区域。手指按住屏幕上拉,底部多出一块白色区域。
在 iOS 中,手指按住屏幕上下拖动,会触发 touchmove
事件。这个事件触发的对象是整个 webview
容器,容器天然会被拖动,剩下的部分会成空白。
移动端触摸事件有三个,分别定义为
1. touchstart :手指放在一个DOM元素上。2. touchmove :手指拖曳一个DOM元素。3. touchend :手指从一个DOM元素上移开。复制代码
显然咱们须要控制的是 touchmove
事件,由此我在 W3C 文档中找到了这样一段话
Note that the rate at which the user agent sends touchmove events is implementation-defined, and may depend on hardware capabilities and other implementation details.
If the preventDefault method is called on the first touchmove event of an active touch point, it should prevent any default action caused by any touchmove event associated with the same active touch point, such as scrolling.
touchmove
事件的速度是能够实现定义的,取决于硬件性能和其余实现细节
preventDefault
方法,阻止同一触点上全部默认行为,好比滚动。
由此咱们找到解决方案,经过监听 touchmove
,让须要滑动的地方滑动,不须要滑动的地方禁止滑动。
值得注意的是咱们要过滤掉具备滚动容器的元素。
实现以下:
document.body.addEventListener('touchmove', function(e) { if(e._isScroller) return; // 阻止默认事件 e.preventDefault();}, { passive: false});复制代码
在不少时候,咱们能够不去解决这个问题,换一直思路。根据场景,咱们能够将下拉做为一个功能性的操做。
好比:下拉后刷新页面
双击或者双指张开手指页面元素,页面会放大或缩小。
HTML 自己会产生放大或缩小的行为,好比在 PC 浏览器上,能够自由控制页面的放大缩小。可是在移动端,咱们是不须要这个行为的。因此,咱们须要禁止该不肯定性行为,来提高用户体验。
HTML meta
元标签标准中有个 中 viewport
属性,用来控制页面的缩放,通常用于移动端。以下图 MDN 中介绍
移动端常规写法
<meta name="viewport" content="width=device-width, initial-scale=1.0">复制代码
所以咱们能够设置 maximum-scale
、minimum-scale
与 user-scalable=no
用来避免这个问题
<meta name=viewport content="width=device-width, initial-scale=1.0, minimum-scale=1.0 maximum-scale=1.0, user-scalable=no">复制代码
监听元素 click
事件,点击元素触发时间延迟约 300ms
。
点击蒙层,蒙层消失后,下层元素点击触发。
iOS 中的 safari,为了实现双击缩放操做,在单击 300ms 以后,若是未进行第二次点击,则执行 click
单击操做。也就是说来判断用户行为是否为双击产生的。可是,在 App 中,不管是否须要双击缩放这种行为,click
单击都会产生 300ms 延迟。
双层元素叠加时,在上层元素上绑定 touch
事件,下层元素绑定 click
事件。因为 click
发生在 touch
以后,点击上层元素,元素消失,下层元素会触发 click
事件,由此产生了点击穿透的效果。
前面已经介绍了,移动设备不只支持点击,还支持几个触摸事件。那么咱们如今基本思路就是用 touch
事件代替click
事件。
将 click
替换成 touchstart
不只解决了 click
事件都延时问题,还解决了穿透问题。由于穿透问题是在 touch
和 click
混用时产生。
在原生中使用
el.addEventListener("touchstart", () => { console.log("ok"); }, false);复制代码
在 vue 中使用
<button @touchstart="handleTouchstart()">点击</button>复制代码
开源解决方案中,也是既提供了 click
事件,又提供了touchstart
事件。如 vant 中的 button
组件
那么,是否能够将 click
事件所有替换成 touchstart
呢?为何开源框架还会给出 click
事件呢?
咱们想象一种情景,同时须要点击和滑动的场景下。若是将 click
替换成 touchstart
会怎样?
事件触发顺序:
touchstart
,touchmove
,touchend
,click
。
很容易想象,在我须要touchmove
滑动时候,优先触发了touchstart
的点击事件,是否是已经产生了冲突呢?
因此呢,在具备滚动的状况下,仍是建议使用 click
处理。
在接下来的fastclick
开源库中也作了以下处理。针对 touchstart
和 touchend
,截取了部分源码。
// If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:// 1) the user does a fling scroll on the scrollable layer// 2) the user stops the fling scroll with another tap// then the event.target of the last 'touchend' event will be the element that was under the user's finger// when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check// is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).this.updateScrollParent(targetElement);复制代码
// Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled// and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).scrollParent = targetElement.fastClickScrollParent;if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {return true;}复制代码
主要目的就是,在使用 touchstart
合成 click
事件时,保证其不在滚动的父元素之下。
使用 npm/yarn
安装后使用
import FastClick from 'fastclick';FastClick.attach(document.body, options);复制代码
一样,使用fastclick
库后,click
延时和穿透问题都没了
按照个人惯例,只要涉及开源库,那么咱们必定要去了解它实现的原理。主要是将现有的原生事件集合封装合成一个兼容性较强的事件集合。
fastclick源码 核心代码不长, 1000 行不到。有兴趣能够了解一下!
Android 手机中,点击 input
框时,键盘弹出,将页面顶起来,致使页面样式错乱。
移开焦点时,键盘收起,键盘区域空白,未回落。
咱们在app 布局中会有个固定的底部。安卓一些版本中,输入弹窗出来,会将解压 absolute
和 fixed
定位的元素。致使可视区域变小,布局错乱。
软键盘将页面顶起来的解决方案,主要是经过监听页面高度变化,强制恢复成弹出前的高度。
// 记录原有的视口高度const originalHeight = document.body.clientHeight || document.documentElement.clientHeight;window.onresize = function(){ var resizeHeight = document.documentElement.clientHeight || document.body.clientHeight; if(resizeHeight < originalHeight ){ // 恢复内容区域高度 // const container = document.getElementById("container") // 例如 container.style.height = originalHeight; }}复制代码
键盘不能回落问题出如今 iOS 12+ 和 wechat 6.7.4+ 中,而在微信 H5 开发中是比较常见的 Bug。
兼容原理,1.判断版本类型 2.更改滚动的可视区域
const isWechat = window.navigator.userAgent.match(/MicroMessenger\/([\d\.]+)/i);if (!isWechat) return;const wechatVersion = wechatInfo[1];const version = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/); // 若是设备类型为iOS 12+ 和wechat 6.7.4+,恢复成原来的视口if (+wechatVersion.replace(/\./g, '') >= 674 && +version[1] >= 12) { window.scrollTo(0, Math.max(document.body.clientHeight, document.documentElement.clientHeight));}复制代码
window.scrollTo(x-coord, y-coord)
,其中window.scrollTo(0, clientHeight)
恢复成原来的视口
头部刘海两侧区域或者底部区域,出现刘海遮挡文字,或者呈现黑底或白底空白区域。
iPhone X 以及它以上的系列,都采用刘海屏设计和全面屏手势。头部、底部、侧边都须要作特殊处理。才能适配 iPhone X 的特殊状况。
设置安全区域,填充危险区域,危险区域不作操做和内容展现。
危险区域指头部不规则区域,底部横条区域,左右触发区域。
具体操做为:
viewport-fit
meta
标签设置为 cover
,获取全部区域填充。判断设备是否属于 iPhone X,给头部底部增长适配层
viewport-fit
有 3 个值分别为:
auto
:此值不影响初始布局视图端口,而且整个web页面都是可查看的。contain
:视图端口按比例缩放,以适合显示内嵌的最大矩形。cover
:视图端口被缩放以填充设备显示。强烈建议使用safe area inset
变量,以确保重要内容不会出如今显示以外。
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes, viewport-fit=cover">复制代码
使用 safe area inset
变量
/* 适配 iPhone X 顶部填充*/@supports (top: env(safe-area-inset-top)){ body, .header{ padding-top: constant(safe-area-inset-top, 40px); padding-top: env(safe-area-inset-top, 40px); padding-top: var(safe-area-inset-top, 40px); }}/* 判断iPhoneX 将 footer 的 padding-bottom 填充到最底部 */@supports (bottom: env(safe-area-inset-bottom)){ body, .footer{ padding-bottom: constant(safe-area-inset-bottom, 20px); padding-bottom: env(safe-area-inset-bottom, 20px); padding-top: var(safe-area-inset-bottom, 20px); }}复制代码
safe-area-inset-top
,safe-area-inset-right
,safe-area-inset-bottom
,safe-area-inset-left
safe-area-inset-*
由四个定义了视口边缘内矩形的top
,right
,bottom
和left
的环境变量组成,这样能够安全地放入内容,而不会有被非矩形的显示切断的风险。对于矩形视口,例如普通的笔记本电脑显示器,其值等于零。对于非矩形显示器(如圆形表盘,iPhoneX
屏幕),在用户代理设置的四个值造成的矩形内,全部内容都可见。
其中 env()
用法为 env( <custom-ident> , <declaration-value>? )
,第一个参数为自定义的区域,第二个为备用值。
其中 var()
用法为 var( <custom-property-name> , <declaration-value>? )
,做用是在 env()
不生效的状况下,给出一个备用值。
constant()
被 css
2017-2018 年为草稿阶段,是否已被标准化未知。而其余iOS 浏览器版本中是否有此函数未知,做为兼容处理而添加进去。
详情请查看文章末尾的参考资料。
在工做中有须要将页面生成图片或者二维码的需求。可能咱们第一想到的,交给后端来生成更简单。可是这样咱们须要把页面代码所有传给后端,网络性能消耗太大。
使用 QRCode 生成二维码
import QRCode from 'qrcode';// 使用 async 生成图片const options = {};const url = window.location.href;async url => { try { console.log(await QRCode.toDataURL(url, options)) } catch (err) { console.error(err); }}复制代码
将 await QRCode.toDataURL(url, options)
赋值给 图片 url
便可
主要是使用 htmlToCanvas
生成 canvas
画布
import html2canvas from 'html2canvas';html2canvas(document.body).then(function(canvas) { document.body.appendChild(canvas);});复制代码
可是不仅仅在此处就完了,因为是 canvas
的缘由。移动端生成出来的图片比较模糊。
咱们使用一个新的 canvas
方法多倍生成,放入一倍容器里面,达到更加清晰的效果,经过超连接下载图片 下载文件简单实现,更完整的实现方式以后更新
const scaleSize = 2;const newCanvas = document.createElement("canvas");const target = document.querySelector('div');const width = parseInt(window.getComputedStyle(target).width);const height = parseInt(window.getComputedStyle(target).height);newCanvas.width = width * scaleSize;newCanvas.height = widthh * scaleSize;newCanvas.style.width = width + "px";newCanvas.style.height =width + "px";const context = newCanvas.getContext("2d");context.scale(scaleSize, scaleSize);html2canvas(document.querySelector('.demo'), { canvas: newCanvas }).then(function(canvas) { // 简单的经过超连接设置下载功能 document.querySelector(".btn").setAttribute('href', canvas.toDataURL());}复制代码
根据须要设置
scaleSize
大小
在微信公众号 H5 开发中,页面内部点击分享按钮调用 SDK,方法不生效。
由于页面内部点击分享按钮没法直接调用,而分享功能须要点击右上角更多来操做。
而后用户可能不知道经过右上角小标里面的功能分享。又想引导用户分享,这时应该怎么作呢?
技术没法实现的,从产品出发。
若是技术上实现复杂,或者直接不能实现。不要强行钻牛角尖哦,学会怼产品,也是程序员必备的能力之一。
在 Hybrid App 中使用 H5 是最多见的不过了,刚接触的,确定会很生疏模糊。不知道 H5 和 Hybrid 是怎么交互的。怎样同时支持 iOS 和 Android 呢?如今来谈谈 Hybrid 技术要点,原生与 H5 的通讯。
使用
DSBridge
同时支持 iOS 与 Android
文档见参考资料
bridge.register
bridge.register('enterApp', function() { broadcast.emit('ENTER_APP')})复制代码
bridge.call
export const getSDKVersion = () => bridge.call('BLT.getSDKVersion')复制代码
const broadcast = { on: function(name, fn, pluralable) { this._on(name, fn, pluralable, false) }, once: function(name, fn, pluralable) { this._on(name, fn, pluralable, true) }, _on: function(name, fn, pluralable, once) { let eventData = broadcast.data let fnObj = { fn: fn, once: once } if (pluralable && Object.prototype.hasOwnProperty.call(eventData, 'name')) { eventData[name].push(fnObj) } else { eventData[name] = [fnObj] } return this }, emit: function(name, data, thisArg) { let fn, fnList, i, len thisArg = thisArg || null fnList = broadcast.data[name] || [] for (i = 0, len = fnList.length; i < len; i++) { fn = fnList[i].fn fn.apply(thisArg, [data, name]) if (fnList[i].once) { fnList.splice(i, 1) i-- len-- } } return this }, data: {}}export default broadcast复制代码
方法调用前,必定要判断 SDK 是否提供该方法 若是 Android 提供该方法,iOS 上调用就会出现一个方法调用失败等弹窗。怎么解决呢?
提供一个判断是否 Android、iOS。根据设备进行判断
export const hasNativeMethod = (name) => return bridge.hasNativeMethod('BYJ.' + name)}export const getSDKVersion = function() { if (hasNativeMethod('getSDKVersion')) { bridge.call('BYJ.getSDKVersion') }}复制代码
同一功能须要iOS,Android方法名相同,这样更好处理哦
调试代码通常就是为了查看数据和定位 bug。分为两种场景,一种是开发和测试时调试,一种是生产环境上调试。
为何有生产环境上调试呢?有些时候测试环境上无法复现这个 bug,测试环境和生产环境不一致,此时就须要紧急生产调试。
在 PC 端开发时,咱们能够直接掉出控制台,使用浏览器提供的工具操做devtools或者查看日志。可是在 App 内部咱们怎么作呢?
使用方法也很简单
import Vconsole from 'vconsole'new Vconsole()复制代码
有兴趣看看它实现的基本原理,咱们关注的点应该在 vsconsole 如何打印出咱们全部 log 的 腾讯开源vconsole
上述方法仅用于开发和测试。生产环境中不容许出现,因此,使用时须要对环境进行判断。
import Vconsole from 'vconsole'if (process.env.NODE_ENV !== 'production') { new Vconsole()}复制代码
操做稍微有点麻烦,不过我会详细写出,大体分为 4 个步骤
sudo npm install spy-debugger -g复制代码
设置手机的 HTTP 代理,代理 IP 地址设置为 PC 的 IP 地址,端口为spy-debugger
的启动端口
spy-debugger 默认端口:9888
Android :设置 - WLAN - 长按选中网络 - 修改网络 - 高级 - 代理设置 - 手动
IOS :设置 - Wi-Fi - 选中网络, 点击感叹号, HTTP 代理手动