本文是Varlet组件库源码主题阅读系列的第九篇。看完这篇文章,你可以学习如何使用div制作点击水波效果。Varlet组件库提供了点击元素时产生水波扩散效果的指令:看法。首先,当指令绑定的目标元素被挂载时,会执行下面的方法:={tasker:null,...(binding.value??{}),touchmoveForbid:binding.value?.touchmoveForbid??context.touchmoveForbid,removeRipple:removeRipple.bind(el),}//绑定一些事件到元素el.addEventListener('touchstart',createRipple,{passive:true})el.addEventListener('touchmove',forbidRippleTask,{passive:true})el.addEventListener('dragstart',removeRipple,{passive:true})文档。addEventListener('touchend',el._ripple.removeRipple,{passive:true})document.addEventListener('touchcancel',el._ripple.removeRipple,{passive:true})}主要是绑定一些事件,处理函数一共有三个,从函数名可以大致看出它的作用。请注意,在addEventListener方法的第三个参数中设置了passive=true。这个选项用来告诉浏览器我们的处理函数中不会调用preventDefault方法。这样做有什么好处?例如,触摸事件或滚动事件的默认行为会触发页面的滚动。如果调用了preventDefault方法,就会阻止滚动,但是问题是浏览器不知道我们在事件处理函数中是否调用了这个方法,所以必须等到函数执行完才能知道,有时候执行该函数的耗时,会导致页面卡顿,所以如果我们的处理函数明明没有调用preventDefault方法,那么直接通过passive标志告诉浏览器,这样浏览器就不会等待和滚动直接,可以显着提高页面性能和体验。先看touchstart事件处理方法createRipple:functioncreateRipple(this:RippleHTMLElement,event:TouchEvent){//首先获取元素上存储的数据const_ripple=this._rippleasRippleOptions//先去掉最后一个水波_ripple。removeRipple()//如果被禁用或最后一个Ripple任务还没有执行则返回if(_ripple.disabled||_ripple.tasker){return}//Rippletaskconsttask=()=>{//...}//保存定时器_ripple.tasker=window.setTimeout(task,60)}当我们触摸并点击一个元素时,它会先移除该元素之前的水波,然后添加一个新的水波任务,这个任务会执行一个60ms的timer之后,然后保存timer的id,为什么不马上执行呢,应该是可以取消的,比如你想在touchmove的情况下不开启水波效果,那么可以cancel这个timer要实现,看一下touchmove事件处理函数forbidRippleTask:functionforbidRippleTask(this:RippleHTMLElement){const_ripple=this._rippleasRippleOptions//触摸移动时需要关闭水波效果吗?if(!_ripple.touchmoveForbid){return}//如果触摸移动在60ms以内,则取消定时器,自然水波效果不存在。_ripple.tasker&&window.clearTimeout(_ripple.tasker)_ripple.tasker=null}接下来看任务方法:functioncreateRipple(this:RippleHTMLElement,event:TouchEvent){//...consttask=()=>{//当定时器任务执行时,清除保存的定时器id_ripple.tasker=null//计算一些数据const{x,y,centerX,centerY,size}:RippleStyles=computeRippleStyles(this,event)//创建一个divconstripple:RippleHTMLElement=document.createElement('div')//添加一个var-ripple类名ripple.classList.add(n())//设置透明度为0,即全透明ripple.style。opacity=`0`//设置位置和比例ripple.style.transform=`translate(${x}px,${y}px)scale3d(.3,.3,.3)`//设置大小ripple。style.width=`${size}px`ripple.style.height=`${size}px`//设置颜色_ripple.color&&(ripple.style.backgroundColor=_ripple.color)//记录创建时间的纹波。dataset.createdAt=String(performance.now())//设置点击元素的样式setStyles(this)//给点击元素添加水波元素this.appendChild(ripple)//修改水的样式20ms后的wave元素,实现水波扩散的动画效果window.setTimeout(()=>{ripple.style.transform=`translate(${centerX}px,${centerY}px)scale3d(1,1,1)`ripple.style.opacity=`.25`},20)}//...可以看到所谓的水波就是一个div。总体流程是先创建一个div元素,然后设置它的透明度为0,初始位置,缩放,大小,背景色,然后添加为被点击元素的子元素,最后在20ms后,修改div的位置、缩放和透明度。只要设置它的transition过渡属性,就可以实现过渡效果,即水波扩散的效果。样式由类名设置var-ripple::root{--ripple-cubic-bezier:cubic-bezier(0.68,0.01,0.62,0.6);--ripple-color:currentColor;}.var-ripple{position:absolute;//设置为绝对定位transition:transform0.2svar(--ripple-cubic-bezier),opacity0.14slinear;//设置过渡效果顶部:0;左:0;border-radius:50%;//设置为圆形opacity:0;will-change:变换,不透明;pointer-events:none;//禁止响应鼠标事件z-index:100;background-color:var(--ripple-color);//背景色}可以看到水波元素是绝对定位的,其他位置的过渡时间是200ms,透明的过渡时间是140ms。接下来我们看一下调用的几个函数。首先是调用computeRippleStyles方法,计算一些基础数据:}:DOMRect=element.getBoundingClientRect()//被点击元素的宽高const{clientWidth,clientHeight}=element//计算波浪圆的半径constradius:number=Math.sqrt(clientWidth**2+clientHeight**2)/2//Diameterconstsize:number=radius*2//...}根据勾股定理计算水波的直径:functioncomputeRippleStyles(element:RippleHTMLElement,event:TouchEvent):RippleStyles{//...//手指点击的位置是相对于被点击元素的坐标constlocalX:number=event.touches[0].clientX-leftconstlocalY:number=event.touches[0].clientY-top//wavee的初始位置lementconstx:number=localX-radiusconsty:number=localY-radius//水波元素的最终位置constcenterX:number=(clientWidth-radius*2)/2constcenterY:number=(clientHeight-radius*2)/2return{x,y,centerX,centerY,size}}size为水波圈的直径;手指点击的位置就是水波圈的初始中心点,然后计算其左上角的坐标x和y作为初始水波元初始位置;水波圈最终的中心点其实就是点击元素的中心点,换算成左上角坐标centerX,centerY就是水波元素的最终位置。因为水波元素是被点击元素的子元素,所以这些坐标是相对的是根据被点击元素的左上角坐标计算的:从绿色圆圈到红色圆圈的过渡,透明度的变化、大小、位置是水波的扩散效应。在给点击的元素添加波浪元素之前,会调用setStyles方法:overflowX='hidden'element.style.overflowY='hidden'position==='static'&&(element.style.position='relative')zIndex==='auto'&&(element.style.zIndex='1')}这个函数的作用主要是检查和设置被点击元素的一些样式。首先overflow需要设置为hidden,否则水波圈的扩散会溢出元素,完全显示。这显然不好看,然后前面提到水波元素是绝对定位的,所以点击的元素的位置不可能是静态的。最终的关卡设置我暂时还没想好能解决什么问题。此时,当我们用手触摸元素时,水波效果就产生了。接下来是删除操作。看一下removeRipple方法:constANIMATION_DURATION=250functionremoveRipple(this:RippleHTMLElement){const_ripple=this._rippleasRippleOptionsconsttask=()=>{//获取波纹元素constripples:NodeListOf
