波纹效果实现

186 阅读3分钟

背景

漫长的开发周期结束了,又到了一年为数不多的闲暇时间,早上刚到公司,先打开早餐,然后打开某些上班的时候不能打开的小网站,左手拿着包子,右手握着鼠标,随意的滑动。啪的一下,鼠标点了下去,一点淡蓝色的波纹随着点击的位置缓缓打开,我惊呼:卧槽,好**炫酷!

image.png

安排

说干就干,包子就着豆浆一下子灌进了口里面,强行咽下去,思考原理

这个波纹说白了就是个圆,从很小的一个点慢慢放大,同时透明度不断变小,直到为0,由于是固定在父元素的,所以父元素需要设置position:relative,目标圆获取点击的位置,通过position:absolute进行定位,top,left获取位置,最后进行动画

干!

准备阶段

首先创建一个dom

  <style>
    .box {
      width: 400px;
      height: 100px;
      background-color: bisque;
    }
  </style>

 <div class="box"></div>

image.png

添加dom

我们需要添加两块dom,一块用于覆盖在box上面,作为触发事件的target,一个就是那个小圆了

window.onload = () => {
     styleDirective(box, { value: 'red' })
 }
const box = document.querySelector('.box')
const styleDirective = (el, bind) => {
        const color = bind.value
        const w = el.getBoundingClientRect().width
        const h = el.getBoundingClientRect().height
        el.style.position = 'relative'
        el.style.overflow = 'hidden'
        const outBox = getOutBoxDom(w, h)
        const BoLang = getBoLangDom(color)
        outBox.appendChild(BoLang)
        el.appendChild(outBox)
}
      

外层覆盖的dom 定位到box的上面。


const getOutBoxDom = (width, height) => {
        let div = document.createElement('div')
        div.style.width = width + 'px'
        div.style.height = height + 'px'
        div.style.position = 'absolute'
        div.style.top = '0'
        div.style.left = '0'
        div.style.overflow = 'hidden'
        div.style.zIndex = '1'
        return div
 }

小圆的dom添加

 const getBoLangDom = color => {
        let div = document.createElement('div')
        div.style.width = '10px'
        div.style.height = '10px'
        div.style.zIndex = '-1'
        div.style.opacity = '0'
        div.style.borderRadius = '10px'
        div.style.backgroundColor = color || 'red'
        div.style.position = 'absolute'
        div.style.top = '0'
        div.style.left = '0'

        return div
   }


给box添加事件

const styleDirective = (el, bind) => {
        const color = bind.value
        const w = el.getBoundingClientRect().width
        const h = el.getBoundingClientRect().height
        el.style.position = 'relative'
        el.style.overflow = 'hidden'
        const outBox = getOutBoxDom(w, h)
        const BoLang = getBoLangDom(color)
        outBox.appendChild(BoLang)
        el.appendChild(outBox)
        el.addEventListener('mousedown', e => {
          const x = e.offsetX
          const y = e.offsetY
          // 截流 单位时间只执行一次
          jieliuUtil(
            () => changeLocation(BoLang, y, x),
            1000,
            () => {
            // 截流的回调函数,截流执行完之后,执行回调函数,删除动画,小圆不展示
              BoLang.style.animation = ''
              BoLang.style.display = 'none'
            }
          )
        })
      }

添加动画


// 动画1s完成
const changeLocation = (div, top, left) => {
        // 小圆定位,并且展示
        div.style.display = 'block'
        div.style.top = top + 'px'
        div.style.left = left + 'px'
        let style = document.styleSheets[0]
        style.insertRule(`@keyframes secondrotate
        {
            0%{
                z-index: 1;
                opacity:1;
                transform:scale(1)
            }
            100%
            {transform:scale(100); opacity:0;}
        }`)
        div.style.animation = `secondrotate 1s ease`
      }

截流函数

const jieliu = () => {
        let ifNext = true
        return (cb, time, callBack) => {
          if (ifNext) {
            ifNext = false
            cb()
            setTimeout(() => {
              callBack && callBack()
              ifNext = true
            }, time)
          }
        }
      }
      const jieliuUtil = jieliu()

最终效果

image.png

vue打包成指令

const styleDirective = (el: HTMLDivElement, bind: DirectiveBinding) => {
  const color = bind.value
  const w = el.getBoundingClientRect().width
  const h = el.getBoundingClientRect().height
  el.style.position = 'relative'
  el.style.overflow = 'hidden'
  const outBox = getOutBoxDom(w, h)
  const BoLang = getBoLangDom(color)
  outBox.appendChild(BoLang)
  el.appendChild(outBox)
  el.addEventListener(
    'mousedown',
    e => {
      //   outBox.style.zIndex = '1'

      const x = e.offsetX
      const y = e.offsetY
      jieliuUtil(
        () => changeLocation(BoLang, y, x),
        1000,
        () => {
          BoLang.style.animation = ''
          BoLang.style.display = 'none'
        }
      )
    },
    true
  )
}
const changeLocation = (div: HTMLDivElement, top: number, left: number) => {
  div.style.display = 'block'
  div.style.top = top + 'px'
  div.style.left = left + 'px'
  let style = document.styleSheets[0]
  style.insertRule(`@keyframes secondrotate
        {
            0%{
                z-index: 1;
                opacity:1;
                transform:scale(1)
            }
            100%
            {transform:scale(120); opacity:0;}
        }`)
  div.style.animation = `secondrotate 1s ease`
}
const getBoLangDom = (color: string) => {
  let div = document.createElement('div')
  div.style.width = '10px'
  div.style.height = '10px'
  div.style.zIndex = '-1'
  div.style.opacity = '0'
  div.style.borderRadius = '10px'
  div.style.backgroundColor = color || 'red'
  ;(div.style.position = 'absolute'), (div.style.top = '0')
  div.style.left = '0'

  return div
}
const getOutBoxDom = (width: number, height: number) => {
  let div = document.createElement('div')
  div.style.width = width + 'px'
  div.style.height = height + 'px'
  div.style.position = 'absolute'
  div.style.top = '0'
  div.style.left = '0'
  div.style.overflow = 'hidden'
  div.style.zIndex = '1'
  return div
}
const jieliu = () => {
  let ifNext = true
  return (cb: Function, time: number, callBack?: Function) => {
    if (ifNext) {
      ifNext = false
      cb()
      setTimeout(() => {
        callBack && callBack()
        ifNext = true
      }, time)
    }
  }
}
const jieliuUtil = jieliu()
export default styleDirective


注册


const app = createApp(App)
  .use(store)
  .use(live2d)
  .use(router)
app.directive('bolang', styleDirective)

使用

<div class="advice" v-bolang="'#aaa'"></div>

结尾

中午了,又是忙碌的一早上,早餐吃的有点多,午餐只加1个鸡腿好了

注:那个小网站其实是我的博客