微信小程序踩坑指南
1. 万恶的button
由于小程序的限制,很多地方必须用到button来做,如分享按钮open-type="share",授权按钮open-type="getUserInfo"等,但是小程序对button做了很多的默认限制:
1.1 默认的padding
button默认设置了padding-left: 14rpx; padding-right: 14rpx
1.2 boder
button的上下border是通过button::after设置的,所以要去除button的boder需要设置
button::after {
border: 0;
}
1.3 button:hover
默认的,小程序会给button加上hover时的样式,所以,如果发现点击了之后button变丑了,就要去设置button-hover样式了,或者直接设置hover-class="none"
,此时无点击效果,这点文档上有说明。
2. 不支持媒体查询
@media screen and (min-device-height:748px) and (max-device-height:812px) and (-webkit-min-device-pixel-ratio:3){
html, body{
height: 2208px;
}
}
H5页面中做iphone X的适配可能会这么写,但是小程序中这样写不生效。
小程序中如果要针对iphone X做兼容,需要使用js,通过wx.getSystemInfo(OBJECT)
获取系统信息
wx.getSystemInfo({
success: res=> {
this.globalData.device = JSON.stringify(res);
let model = res.model
// 判断是否是iPhone X
if(model.toLowerCase().search('iphone x') != -1){
this.globalData.isIphoneX = true;
}else{
this.globalData.isIphoneX = false;
}
}
})
3. 关于背景图片的使用
在wxss文件中,不能引用本地图片作为背景图片,否则,即便开发者工具上预览正常,真机上是读不出图片的,解决办法是:将本地图片转为base64格式,或者是使用网络图片
4. 关于scroll-view组件的使用
4.1 scroll-view
横向滑动
当使用scroll-view组件作scroll-x横向滑动时,scroll-view层需要设置white-space: nowrap;
,scroll-view中的子项必须使用display: inline-block;
做布局,不能使用float
浮动布局,否则组件会失效。
另外,默认情况下,横向滚动块底部会有一个滚动条,要隐藏滚动条,需要设置:
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
这一点在官方文档中也没有提及。
scroll-view做竖向滑动时,需要给scroll-view
一个固定的高度,尽量不要通过给scroll-view
的父盒子使用flex布局,然后scroll-view
层使用flex:1;
来设置高度,否则在ios上可能会出现一些无法滑动等不可预知的问题。
4.2 scroll-view
竖向滚动
scroll-view
竖向滚动时绑定bindscroll="scrollHandler"
滚动监听时,上拉加载数据除了要判断是否已经获取到最后一页数据后防止继续发出接口请求,还要在每次接口请求发出之时判断请求是否已经回来,在请求未回来之前禁止继续发送请求。
export default {
data() {
return {
isLoadEnd: true, // 用于判断当前请求是否已完成
isLoadAll: false // 用于判断是否已经加载了所有数据,所有数据加载完后不再发送请求
};
},
methods: {
scrollHandler() {
if (!this.isLoadEnd || this.isLoadAll) return;
this.isLoadEnd = false;
// 下面是发送请求
ajax.getData().then(res=> {
this.isLoadEnd = true;
// 这里根据接口返回的数据判断是否数据已经全部加载,全部加载设置 this.isLoadAll = true;
}).catch(err=> {
this.isLoadEnd = true;
});
}
}
};
5. canvas绘制网络图片
使用canvas绘制图片时,不能使用网络图片,因此如果要绘制网络图片,需要使用downloadFile
或getImageInfo
将图片缓存到本地,另外还要在小程序后台配置好downloadFile
安全域名,否则在真机上会出现奇怪的现象,打开调试模式图片就加载正常,关闭调试模式就加载不出来。
const ctx = wx.createCanvasContext('myCanvas')
const url = '网络图片路径'
wxGetImageInfo (url) {
return new Promise((resolve, reject) => {
// 获取图片信息。网络图片需先配置download域名才能生效。
wx.getImageInfo({
src: url,
success: res => {
resolve(res)
},
fail: e => {
reject(e)
}
})
})
}
const {path} = wxGetImageInfo(url)
ctx.drawImage(path, 0, 0, canvasWith, canvasHeight)
ctx.draw(fakse, () => {
wxApi.wxCanvasToTempFilePath({
width: 750 * dpr,
height: 1138 * dpr,
canvasId: 'myCanvas'
}).then(res => {
this.tempImg = res.tempFilePath
wxApi.hideLoading()
}).catch(() => {
wxApi.hideLoading()
})
})
- 图片绘制成功要进行预览,则要调用 wx.canvasToTempFilePath() 将画布内容导出生成图片;
- 图片导出成功后得到的 tempFilePath 需要放在 image 组件src中,不能用做 background-image 的 url ,否则真机下会显示不出图片;
- 将得到的 tempFilePath 传入 wx.saveImageToPhotosAlbum() 保存到相册;
- 保存到相册需要用户授权,因此要考虑用户拒绝授权的情况,拒绝之后需要将保存图片的按钮变为 <button open-type="openSetting"></button>打开设置页面;
- 因为要考虑到新老用户是否授权的情况,需要两个不同的button,另外还要利用到wx.getSetting()来获取授权信息,这里会相比比较麻烦;
6. 弹窗遮罩层滚动穿透问题
弹窗最外层元素上绑定禁止滚动事件 catchtouchmove="touchmoveHandler"
<view catchtouchmove="touchmoveHandler>
<scroll-view scroll-y>这里写弹窗的具体内容</scroll-view>
</view>
// js部分
touchmoveHandler () {
return false;
}
- 针对 catchtouchmove 方法,原生小程序使用 catchtouchmove="touchmoveHandler",mpvue 中使用 @touchmove.stop="touchmoveHandler",注意 touchmoveHandler 要给个空方法,否则会报警
- 绑定 catchtouchmove="touchmoveHandler" 后,弹窗内部如果也有滚动区域的话,则弹窗内部的滚动也会失效,即 catchtouchmove 禁止了所有区域的滚动行为;
- 针对第2点,如果弹窗内还有滚动条,则不能使用catchtouchmove方法,按照H5的做法,给 页面 最外层添加
height:100%;overflow:hidden;
,但是会出现弹窗出来时页面会有一个返回顶部的toTop现象,交互很不友好,并且在ios(某些机型)上会有bug:先在滚动区域滚动几下,然后再在外层滚动几下,此时再回到滚动区域滑动会滑动不了; - 针对第3点bug,如果一定要解决滚动穿透,按照1的方法,只是在弹窗区域内需要滚动的地方使用
scroll-view
进行包裹。
.noScroll {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 0;
overflow: hidden;
}
并且需要动态获取页面当前滚动的距离,然后动态修改containerView的top值,这样做相对麻烦,因此建议此类情况允许穿透;
7. 兼容问题
7.1 onPullDownRefresh
下拉刷新兼容问题
使用onPullDownRefresh
下拉刷新时,如果将wx.stopPullDownRefresh()
放在接口请求的回调中执行并且在请求过程中使用了小程序自带的showToast
或showLoading
等交互反馈时,在ios上会出现下拉之后反弹过大页面不会回弹至顶部的问题,android上正常。目前解决方法是:
onPullDownRefresh: function () {
this.getInfo() // 发送请求
wx.stopPullDownRefresh() // 关闭下拉刷新
}
// 或者是加个定时器
onPullDownRefresh: function () {
this.getInfo() // 发送请求
setTimeout(()=> {
wx.stopPullDownRefresh() // 关闭下拉刷新
}, 800)
}
参考: https://blog.csdn.net/weichen913/article/details/79360658
7.2 font-weight
设置字体加粗的兼容问题
小程序中设置font-weight
数字时,在android
机上无效。只有700
和bold
有效。
7.3 scroll-view
设置height:100%
引起的滑动卡顿问题
部分android机上,在scroll-view
上设置height:100%
会导致页面上滑时不流畅的卡顿问题,不设置自动撑开即可。
8. 摇一摇 - 加速度计
由于小程序目前没有提供摇一摇的API,因此要实现摇一摇的功能需要借助加速度计来实现,与加速度计相关的API有3个:监听加速度数据wx.onAccelerometerChange(CALLBACK)
、开始监听加速度数据wx.startAccelerometer(OBJECT)
和停止监听加速度数据wx.stopAccelerometer(OBJECT)
。
然后,坑来了。。。
wx.stopAccelerometer(OBJECT)
停止监听,这个方法是停止了监听,再晃动手机不会走回调方法了,但是它并不移除监听。
那么问题来了,在某个页面每调用一次第一个api它就会注册一个监听,第三个api并不能移除掉监听,导致你再次开启的时候就会有多个回调方法在同时进行。就好像是只提供了addEventListener
,但却没有提供removeEventListener
。
如果小程序中有多个页面需要监听加速度,如A、B两个页面,A页面,onReady
事件后wx.onAccelerometerChange
,离开A页面,stopAccelerometer
停止监听。到了B页面,A页面的事件还存在。在B页面摇一摇,就会触发两个页面的回调。
解决方法:
在微信小程序启动的时候就调用监听方法,在回到方法中通过getCurrentPages()
获取到当前的页面,在当前页面调用你想回调的方法。
onLaunch: function () {
wx.onAccelerometerChange((e) => {
var pages = getCurrentPages()
var currentPage = pages[pages.length - 1]
if (currentPage.onAccelerometerChange) {
currentPage.onAccelerometerChange(e)
}
})
}
9. video组件
- 在小程序中,
video
组件的层级跟canvas组件一样,层级是最高的,设置z-index
无效; - 使用 API
wx.createVideoContext(videoId)
的seek()
方法时,要求视频资源必须是网络资源,不能是本地的视频资源,否则seek()
设置无效; - 如果
video
内部设置了autoplay="{{true}}"
自动播放,在 ios 下,即便设置过this.videoContext.pause()
,视频一加载也会触发组件的bindplay
事件,而 android 下不会;
10. mpvue
中关于data的值不会重置的问题
mpvue
中,放在data中的数据,在页面没有销毁(点击左上角或手机返回键返回上一层,再进入页面)的情况下,data的数据不会进行重置,因此在小程序onUnload的时候要将数据进行一次重置,也可以在onLoad或onShow生命周期数据操作之前使用Object.assign(this.$data, this.$options.data());
将所有的数据重置为初始值;
11. ad
组件 - Banner广告
小程序的banner广告会存在加载失败的情况,应该在监听到加载成功的时候再将广告显示出来,否则即便失败的情况,广告也会在页面中占位,显示的 暂无广告 的样式。并且需要将isLoadAd放在父容器上,不能直接在ad
组件上使用v-if
做判断,否则不生效。
<view :class="{hide: !isLoadAd}">
<ad unit-id="adunit-****" @load="adLoadSuccess" @error="adLoadFail"></ad>
</view>
data() {
return {
isLoadAd: true
};
}
// 广告加载成功监听
adLoadSuccess() {
this.isLoadAd = true;
},
// 广告加载失败监听
adLoadFail() {
this.isLoadAd = false;
}
12. 激励视频
wx.createRewardedVideoAd(Object object)
创建一个激励式视频广告,返回一个单例对象,该对象仅对单个页面有效,不允许跨页面使用。多次创建,将返回同一个激励式视频广告对象(RewardedVideoAd)。该方法在基础库 2.6.0 开始支持,开发者需做兼容处理。
注意:
激励视频会存在加载失败的情况,因此所有观看视频相关的操作必须是在视频加载成功后(isLoadedVideoAd=true
)才执行。
export default {
data() {
this.videoAd = null;
return {
isLoadedVideoAd: false // 激励视频是否加载成功,默认不展示
};
},
onLoad() {
this.createVideoAd(); // 创建激励视频
},
onUnload() {
this.destoryVideoAd(); // 销毁激励视频及相关视频监听事件
},
methods: {
// 创建激励视频
createVideoAd() {
if (wx.createRewardedVideoAd && !this.videoAd) {
this.videoAd = wx.createRewardedVideoAd({
adUnitId: '你的视频广告id'
});
this.videoAdLoadHandler();
this.videoAdErrorHandler();
this.videoAdCloseHandler();
},
// 监听激励视频是否加载成功
videoAdLoadHandler() {
this.videoAd.onLoad(()=> {
this.isLoadedVideoAd = true;
});
},
// 监听视频加载出错
videoAdErrorHandler() {
this.videoAd.onError(()=> {
// 视频加载出错,请稍后重试
this.deleteVideoAd();
});
},
// 监听视频的播放完成事件
videoAdCloseHandler() {
this.videoAd.onClose(status=> {
// 2.1.0以前的版本兼容,status是一个undefined
if ((status && status.isEnded) || status === undefined) {
// 正常播放结束-观看完成的逻辑写在这里
} else {
// 播放中途退出-相应的处理逻辑写在这里
}
});
},
// 销毁激励视频广告组件
destoryVideoAd() {
if (this.videoAd) {
this.videoAd.offLoad();
this.videoAd.offClose();
this.videoAd.offError();
this.videoAd = null;
this.isLoadedVideoAd = false;
}
},
// 显示激励视频广告
videoAdShow() {
if (this.videoAd && this.videoAd.show && typeof this.videoAd.show === 'function') {
this.videoAd.show().catch(()=> {
// 失败后重试
this.videoAd.load().then(()=> {
this.videoAd.show(); // 加载成功重新显示视频广告
}).catch(()=> {
// 视频加载失败,请稍后重试
this.deleteVideoAd();
});
});
}
}
}
}
};
参考:https://wximg.qq.com/wxp/pdftool/get.html?post_id=851
13. webView - 小程序跳H5
web-view
组件: 承载网页的容器。会自动铺满整个小程序页面。
由于web-view
组件会自动铺满整个小程序页面,因此每个小程序要跳H5的链接,都需要一个中间页来承载。
而如果一个小程序中如果有多个要跳H5的链接,是不是就需要创建多个承载页?
当然不需要,否则就太麻烦了。因此,建立一个承载页,然后将web-view
的src传入即可。
<template>
<div class="webv-container">
<web-view :src="webSrc"></web-view>
</div>
</template>
<script>
export default {
data() {
return {
webSrc: ''
};
},
onLoad() {
const rootQuery = this.$root.$mp.query || {};
this.webSrc = decodeURIComponent(rootQuery.src);
}
};
</script>
tip:
- 父级页面传入src时一定要进行encodeURIComponent,然后在webView页面中进行decodeURIComponent;
参考:
http://www.iamaddy.net/2017/07/wxapp-accelerometer/
https://blog.csdn.net/frankkay/article/details/80485095