源码
扯淡
- 我:
jym
想死你们了,沉寂多天,我带着高质量文章回来了, - jym:你谁啊? 爱写不写!!
额,写写写,即使你们不看,那我也得写给自己看,将自己的心得体会、问题踩坑归纳总结,来塑造自己的职业经历
因为我一直在笃信人都是经历塑造的,你干了什么事,有什么职业经历,永远比你卷了什么题重要。
此时我相信,很多痛斥八股的jym
准备哆哆嗦嗦站起来,大声喊道:终于该我演了!!
额,那个,坐下,别这么激动,我还没说完,
虽然,干了什么事情很重要,
但不是说卷题不重要啊,因为不卷题,你就干不了什么事,单位你都进不去,你能干个锤子,
有jym说,进不去不进了,我干个体户,同志们,个体户现在也不好混啊,那些个技术个体户,也天天接不到广告,挣不到钱,急眼了,天天靠着chatgpt 使劲的挥舞着镰刀呢!!!
所以大家还是要辩证的看问题,只是对于整个职业生涯来说,经历很重要,
因为你会老啊,你终究干不过年轻人啊,你加班到底还是加不过他啊,属于你的时代最终会结束啊,但是洗尽铅华之后,能够留下来的,或者说能够在这个行当,扎下根来不被替代的,一定是你的宝贵经验,你干了什么事,踩过什么坑,写过什么项目,总结出来什么方案
这都是你不世出的经历,这才是年轻人代替不了的
把这些东西,嘡!嘡!嘡!往桌子上一摆,老板能不给你晋升专家?
- 老板:干得了就干,干不了就滚!
- 我:干(行情不好,劳资忍了)!
想说的话说完了,我们言归正传
为什么webapp体验很差
在我们现在的大多数app中,大家都会发现,基本清一色的使用原生开发,只有在不重要的页面中,才会使用webapp,也就是所谓的h5页面
之所以是h5无法替代原生除了审核因素之外,原因很简单,它不能编译成native
,只能通过容器这个介质,也就是webview
,去运行h5页面,但是这样的话性能就会大大折扣
你想啊,我去打开一个页面,还需要先初始化容器,然后要下载页面,然后还要受webview
的限制,他能好的了吗?
其实,理论上来说web页面是可以通过编译器编译编译成native的,然而现实是,谷歌
的这帮大佬玩了命的优化,折腾,于是诞生了flutter
这种从头再来的产物,这就说明,理论他就是一坨….
当然,值得庆幸的是,web技术的快速发展中,我们可以无限接近,根据我骥某人的钻研,在交互比较复杂h5页面中,我们可以利用以下三点
- 1、利用css3
- 2、利用requestAnimationFrame
- 3、利用离线包,解决下载资源问题
前面两个不过多解释了DDDD(懂得都懂)
我们来解释一下离线包的问题,所谓离线包,就是在app
中利用资源请求拦截匹配本地资源,从而大大提升页面加载速度
所谓光说不练假把式
,我们手把手打造一个
手把手打造抽屉指令组件
滑动抽屉是常用的交互体验,也在app中随处可见,那么我们h5该如何实现呢?
且听我慢慢将来
基本布局
如上图所示,我们首先要实现一个基本布局,来做一个抽屉收起的状态
代码如下:
<template>
<div class="box">
<div class="list" v-swipe-action="action">
<div class="darg">拖动区域</div>
<div class="content1">
<div class="content">好好学习</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const action = reactive({
hotRegion: '.darg',
slide: '.content1',
safeDistance: 0,
initHeight: '200',
})
setTimeout(() => {
action.safeDistance = 200
// action.value = {
// hotRegion: '.darg',
// slide: '.content1',
// safeDistance: 200,
// initHeight: '200',
// }
}, 2000)
</script>
<style scoped lang="scss">
.box {
height: 100vh;
position: relative;
overflow: hidden;
.list {
position: absolute;
width: 100%;
left: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 1);
border-top-left-radius: 16px;
border-top-right-radius: 16px;
z-index: 10;
padding: 0 19px;
padding-top: 30px;
// 解决ios 中由于vw vh 布局方式导致的边线裁剪问题
margin: 0 1px;
box-sizing: border-box;
//transition: bottom 0.2s cubic-bezier(0, 0, 0.48, 1);
#dataCollectionId {
overflow: hidden;
height: 100%;
overscroll-behavior: none;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
}
.darg {
font-size: 20px;
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: center;
color: #000;
}
.content1 {
overflow: auto;
height: 100%;
box-sizing: border-box;
}
.content {
color: #000;
font-size: 150px;
height: 2000px;
}
}
}
</style>
当基本布局完成后我们就可以开始处理逻辑了
手势
既然是抽屉,那么必须要有滑动
,拖动
,等手势操作,于是在经过一番筛选之后,我选择了腾讯的一个手势开源插件
alloyfinger
之所以选择它,没有什么特殊的理由,原因很简单,他是中国人写的啊,亲切,我就乐意用!!
所以说,这个世上很多事,本不需要什么冠冕堂皇的理由,之所以需要理由,是因为很多人喜欢找抽,抽久了,就需要理由了。
使用方式也非常简单,代码如下:
var af = new AlloyFinger(element, {
touchStart: function () { },
touchMove: function () { },
touchEnd: function () { },
touchCancel: function () { },
multipointStart: function () { },
multipointEnd: function () { },
tap: function () { },
doubleTap: function () { },
longTap: function () { },
singleTap: function () { },
rotate: function (evt) {
console.log(evt.angle);
},
pinch: function (evt) {
console.log(evt.zoom);
},
pressMove: function (evt) {
console.log(evt.deltaX);
console.log(evt.deltaY);
},
swipe: function (evt) {
console.log("swipe" + evt.direction);
}
});
而在我们的代码中使用的手势远没有这么多我们只需要滑动
、拖动
足以,代码如下:
const pressMove = (event) => {
if (isScroll(event.target)) return
bottom += -event.deltaY
bottom >= 0 && (bottom = 0)
// 设置动画
setTransition(el, false)
setBottom(el, bottom)
}
const touchEnd = (event) => {
if (isScroll(event.target)) return
if (event.direction == 'Up') {
bottom = 0
} else if (event.direction == 'Down') {
bottom = minBottom
} else if (typeof event.direction == 'undefined') {
bottom = lastHeight
}
// 设置动画
setTransition(el, true)
setBottom(el, bottom)
lastHeight = bottom
}
af = new AlloyFinger(el, {
pressMove,//拖动手势
touchEnd,
swipe: touchEnd,// 滑动手势
})
好了,我们一个抽屉的基本功能就完成了,
但是让你值钱的,不是这个玩意,这玩意你能干,别人也能干
去优化体验问题的经验才是我们应该学习的,这才是各位jym
安身立命的资本!
为了优化体验问题,我们还需要解决几个问题,才能形成一个接近原生体验的组件
需要解决的问题
- 1、抽屉内的滚动条滑动和拖动冲突问题如何解决?
- 2、抽屉拖动的性能问题如何解决
- 3、手势滑动抽屉的动效问题该如何解决
jym
不要着急,我们接下来一个个来,从丘处机路过牛家村开始
抽屉内的滚动条滑动和拖动冲突问题如何解决?
当我们使用了简单的抽屉体验之后,大家就会发现,抽屉中一旦有滚动条就歇菜了,滚动条会和拖动事件冲突,
那么怎么办呢?
其实,细想一下,我们就可以发现,我们可以判定滚动条是否已经到顶部,当滚动条不在顶部的时候,我们就关闭拖动事件,当他在顶部的时候,我们就开启
这样一来,就可以将滚动和拖动事件,变成相当于单线程的事件
,判断代码如下:
const isScroll = (target) => {
setOverflow(slideTarget, bottom >= 0)
const scrollHeight = slideTarget.scrollHeight
const clientHeight = slideTarget.clientHeight
const scrollTop = slideTarget.scrollTop
if (
slideTarget &&
slideTarget.contains(target) &&
scrollHeight != clientHeight &&
bottom >= 0 &&
scrollTop != 0
) {
return true
}
return false
}
抽屉拖动的性能问题如何解决
我们知道,在web页面中,由于频繁的操作dom
会引起页面的卡顿,大家通识的优化方案有两个
- 1、节流函数,节制事件的触发评率
- 2、利用requestAnimationFrame函数优化性能
别急我们一个个分析
理论上来说,节流函数,其实是最优的选择,将事件的触发频率降低,那么操作dom频率就会降低,从而解决性能问题,在辅以requestAnimationFrame
函数
可谓巧夺天工
然而,在实际的使用过程中,拖动抽屉的时候,在粗鲁之辈的暴力测试中,由于节流函数的限制,他却不跟手,性能是好了,体验却极差
这是两瓶毒药啊?
怎么办?
遵循两权相害取其轻
原则,更遵循有一个能跑原则
我们只能取消节流函数!只使用利用requestAnimationFrame
函数
代码如下:
export const requestAnimationFrame = (callback) => {
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (callback) {
return window.setTimeout(callback, 1000 / 60)
}
}
window.requestAnimationFrame(callback)
}
这说明什么?,大家以后千万不要有技术人的执念
,总是,事事技术为先
,性能为尊
,殊不知,你好我好大家好,才是最优解!
性能不好又怎么样呢? 测试同学
开心了,不好吗?
手势滑动抽屉的动效问题该如何解决
这个问题就比较好解决了,之所以需要解决这个问题,原因很简单,我们拖动的时候,是不能有动画的,因为它是js 的实时计算,为了让他能跟手
但是,但是当我们划动
的时候,就需要有一个效果缓缓弹出和收起了。
而我们的处理方式也非常简单,只需要在合适时机,添加动画即可
代码如下:
export const setTransition = (el, open) =>
(el.style.transition = open ? 'bottom 0.2s cubic-bezier(0, 0, 0.48, 1)' : '')
好了,整个组件的难点全部讲解完毕了,希望各位jym
有所收获
我将上述组件抽成了一个指令,附上完整代码,方便理解
完整指令代码
import AlloyFinger from 'alloyfinger'
import {
setOverflow,
setBottom,
addEventListener,
setTransition,
} from './utils'
export default {
install(app) {
let af = null
let bottom = 0
let minBottom = 0
let height = 0
let lastHeight = 0
const setHeight = (el, value) => {
height = document.body.clientHeight - (value?.safeDistance || 0)
minBottom = -(height - (value?.initHeight || height / 2))
bottom = minBottom
lastHeight = bottom
el.style.height = `${height}px`
setBottom(el, bottom)
}
app.directive('swipe-action', {
mounted(el, binding) {
// 带滚动条的容器
const slideTarget =
(binding.value?.slide &&
document.querySelector(binding.value.slide)) ||
el
setOverflow(slideTarget, false)
// 设置默认高度
setHeight(el, binding.value)
// 设置动画
setTransition(el, true)
if (binding.value?.hotRegion) {
const target = document.querySelector(binding.value.hotRegion)
addEventListener(
'click',
() => {
bottom >= 0 ? (bottom = minBottom) : (bottom = 0)
setOverflow(slideTarget, bottom >= 0)
setBottom(el, bottom)
lastHeight = bottom
},
{
target,
},
)
}
const isScroll = (target) => {
setOverflow(slideTarget, bottom >= 0)
const scrollHeight = slideTarget.scrollHeight
const clientHeight = slideTarget.clientHeight
const scrollTop = slideTarget.scrollTop
if (
slideTarget &&
slideTarget.contains(target) &&
scrollHeight != clientHeight &&
bottom >= 0 &&
scrollTop != 0
) {
return true
}
return false
}
const pressMove = (event) => {
if (isScroll(event.target)) return
bottom += -event.deltaY
bottom >= 0 && (bottom = 0)
// 设置动画
setTransition(el, false)
setBottom(el, bottom)
}
const touchEnd = (event) => {
if (isScroll(event.target)) return
if (event.direction == 'Up') {
bottom = 0
} else if (event.direction == 'Down') {
bottom = minBottom
} else if (typeof event.direction == 'undefined') {
bottom = lastHeight
}
// 设置动画
setTransition(el, true)
setBottom(el, bottom)
lastHeight = bottom
}
af = new AlloyFinger(el, {
pressMove,
touchEnd,
swipe: touchEnd,
})
},
updated(el, binding) {
if (binding.value.safeDistance) {
setHeight(el, binding.value)
}
},
unmounted() {
af && af.destroy()
},
})
},
}