效果
实现思路:在点击位置添加一个span, 从scale(0) 变化到scale(1)。过渡结束后删除这个span。
click事件中,计算span位置时没有使用
ev.offsetX
。应为当元素内部有其他元素时,ev.target
可能并不是这个元素本身,ev.offsetX
是相对于元素自身的坐标。当ev.target != el
时span的位置是错误的。
直接上代码 ripple.ts
这里简单实现了一下,属性
duration,background
只能在初始化设置,因为只实现了mounted
, 要使其响应属性变化,需要实现updated(el, binding, vnode, prevVnode) {}
接口
import type { Directive } from "vue"
export const vRipple: Directive<
HTMLElement,
{
duration?: number
backgroundColor?: string
}
> = {
mounted(el, binding, vnode, prevVnode) {
let { position, overflow } = getComputedStyle(el)
if (position == "static") {
el.style.position = "relative"
}
if (overflow !== "hidden") {
el.style.overflow = "hidden"
}
el.addEventListener("click", function doRipple(ev: MouseEvent) {
let span = document.createElement("span")
let size = Math.max(el.offsetWidth, el.offsetHeight) * 2
let duration = (binding.value?.duration ?? 500) / 1000
let backgroundColor = binding.value?.backgroundColor ?? "rgba(0, 0, 0, .15)"
let { x, y } = el.getBoundingClientRect()
let { clientX, clientY } = ev
span.style.width = size + "px"
span.style.height = size + "px"
span.style.position = "absolute"
span.style.backgroundColor = backgroundColor
span.style.top = clientY - y - size / 2 + "px"
span.style.left = clientX - x - size / 2 + "px"
span.style.transform = "scale(0)"
span.style.transition = `transform ${duration}s ease-in, opacity ${duration}s ease-in`
span.style.borderRadius = "50%"
span.style.pointerEvents = "none"
el.appendChild(span)
span.addEventListener("transitionend", function (e: TransitionEvent) {
span.parentElement?.removeChild(this)
})
requestAnimationFrame(() => {
span.style.transform = "scale(1)"
span.style.opacity = "0"
})
})
}
}
怎么使用,vue3+setup 直接引入即可,v开头驼峰命名的变量会自动识别为指令vue3文档 / 自定义指令
<template>
<div v-ripple="{ duration: 1300 }" style="width: 500px; height: 100px; background-color: blueviolet"></div>
</template>
<script lang="ts" setup>
import { vRipple } from "@/directives/ripple"
</script>
直接引入,插件可以识别, 指令提示+属性提示,不用额外配置, 看图。
下面是全局引入,并且配置代码提示。
global.d.ts
在tsconfig.config中要包含这个文件,不然没有效果
{ "include": ["env.d.ts", "global.d.ts", "src/**/*", "src/**/*.vue"] }
export {}
declare module "vue" {
interface ComponentCustomProperties {
vRipple: import("vue").Directive<
HTMLElement,
{
duration?: number
backgroundColor?: string
}
>
}
}
main.ts
import { vRipple } from "./directives/ripple"
app.directive("ripple", vRipple)
只要.d.ts文件正常工作,编译器就会有提示,但是输入v-ripple时没有提示,设置value时会有提示,看图
不能像直接引入一样有提示,只能手输。(有懂得可以留言分享一下)
属性提示会有
vue 完整代码
<template>
<div>
<div
v-ripple="{ backgroundColor: 'rgba(255,255,255,.2)' }"
style="width: 100px; height: 100px; background-color: black"
></div>
<el-divider />
<div v-ripple="{ duration: 1300 }" style="width: 500px; height: 100px; background-color: blueviolet"></div>
<el-divider />
<div v-ripple="{ duration: 1000 }" style="width: 300px; height: 100px; background-color: aquamarine"></div>
<el-divider />
<el-button v-ripple size="large">test ripple</el-button>
<el-button v-ripple size="large" type="primary">test ripple</el-button>
<el-button v-ripple size="large" type="info">test ripple</el-button>
<el-divider />
</div>
</template>
<script lang="ts" setup>
import { vRipple } from "@/directives/ripple"
</script>
<style lang="scss" scoped></style>