波纹效果
假设 .waves-ripple
类是实现波纹效果的样式,那么它在初始时是一个大小固定的透明的圆,圆心是鼠标点击的地方。在结束时它的直径是目标元素对角线的长度,而且圆心是目标元素的中心。波纹效果就是通过改变它的透明度、伸缩性、位置再加上过渡来实现的。

监听目标元素的 mousedown
事件,当事件被触发时,在目标元素下创建一个 div,并赋予其类(.waves-ripple
),然后动态更改其样式(opacity, transform)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| <html> <body> <div id="box" class="waves"></div> </body>
<script> onload = () => { const el = document.getElementById("box");
function setConvertStyle(obj) { let style = ""; for (let i in obj) { if (obj.hasOwnProperty(i)) style += `${i}:${obj[i]};`; } return style; }
function onCurrentClick(event) { const diameter= Math.sqrt(el.clientWidth**2 + el.clientHeight**2) const centerX = el.clientWidth / 2; const centerY = el.clientHeight / 2; const scaleNum = diameter / 20;
const divEl = document.createElement("div"); divEl.classList.add("waves-ripple"); el.appendChild(divEl); const style = { left: `${event.layerX}px`, top: `${event.layerY}px`, opacity: 1, transform: `scale(${scaleNum}) translate(${(centerX-event.layerX)/scaleNum}px,${(centerY-event.layerY)/scaleNum}px)`, transtion: "all 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940)" } divEl.setAttribute("style", setConvertStyle(style));
setTimeout(() => { divEl.setAttribute("style", setConvertStyle({ left: style.left, top: style.top, opacity: 0, transform: style.transform })) setTimeout(() => { el.removeChild(divEl); }, 700) }, 450) }
el.addEventListener("mousedown", onCurrentClick, false); } </script>
<style> .waves { position: relative; width: 200px; height: 200px; background-color: #1095c1; overflow: hidden; cursor: pointer; } .waves-ripple { position: absolute; width: 20px; height: 20px; opacity: 0; margin: -10px 0 0 -10px; border-radius: 50%; background-color: rgba(0, 0, 0, 0.2); pointer-events: none; transition: all 0.7s ease-out; } </style> </html>
|
注意: 由于圆是在变大的同时进行移动的,那么它最终的位置与我们预想的位置不一样。假设我们想要它移动 translate(x, y)
,但它最终移动了 translate(scaleNum*x, scaleNum*y)
。因此,要想将它移动到某个位置,需要移动 translate(x/scaleNum, y/scaleNum)
。
vue3 波纹指令
src/style/index.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| .waves-effect { position: relative; cursor: pointer; display: inline-block; overflow: hidden; user-select: none; -webkit-tap-highlight-color: transparent; vertical-align: middle; z-index: 1; will-change: opacity, transform; } .waves-effect .waves-ripple { position: absolute; border-radius: 50%; width: 20px; height: 20px; margin-top: -10px; margin-left: -10px; opacity: 0; background: rgba(0, 0, 0, 0.2); transition: all 0.7s ease-out; pointer-events: none; } .waves-effect.waves-light .waves-ripple { background-color: rgba(255, 255, 255, 0.45); } .waves-effect.waves-red .waves-ripple { background-color: rgba(244, 67, 54, 0.7); } .waves-effect.waves-yellow .waves-ripple { background-color: rgba(255, 235, 59, 0.7); } .waves-effect.waves-orange .waves-ripple { background-color: rgba(255, 152, 0, 0.7); } .waves-effect.waves-purple .waves-ripple { background-color: rgba(156, 39, 176, 0.7); } .waves-effect.waves-green .waves-ripple { background-color: rgba(76, 175, 80, 0.7); } .waves-effect.waves-teal .waves-ripple { background-color: rgba(0, 150, 136, 0.7); }
|
src/directive/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| import type { App } from "vue";
export function wavesDirective(app: App) { app.directive("waves", { mounted(el, binding) { el.classList.add("waves-effect"); binding.value && el.classList.add(`waves-${binding.value}`); function setConvertStyle(obj: { [key: string]: unknown }) { let style: string = ''; for (let i in obj) { if (obj.hasOwnProperty(i)) style += `${i}:${obj[i]};`; } return style; } function onCurrentClick(e: { [key: string]: any }) { const diameter: number = Math.sqrt(el.clientWidth**2 + el.clientHeight**2) const centerX: number = el.clientWidth / 2; const centerY: number = el.clientHeight / 2; const scaleNum: number = diameter / 20;
let elDiv = document.createElement('div'); elDiv.classList.add('waves-ripple'); el.appendChild(elDiv); let styles = { left: `${e.layerX}px`, top: `${e.layerY}px`, opacity: 1, transform: `scale(${scaleNum}) translate(${(centerX-e.layerX)/scaleNum}px,${(centerY-e.layerY)/scaleNum}px)`, 'transition-duration': `700ms`, 'transition-timing-function': `cubic-bezier(0.250, 0.460, 0.450, 0.940)`, }; elDiv.setAttribute('style', setConvertStyle(styles));
setTimeout(() => { elDiv.setAttribute( 'style', setConvertStyle({ opacity: 0, transform: styles.transform, left: styles.left, top: styles.top, }) ); setTimeout(() => { elDiv && el.removeChild(elDiv); }, 700); }, 450); } el.addEventListener('mousedown', onCurrentClick, false); }, unmounted(el) { el.addEventListener("mousedown", () => {}); } }); }
|
main.ts
1 2 3 4 5 6
| import "@/style/index.css" import { wavesDirective } from "@/directive";
const app = createApp(App) wavesDirective(app) app.mount('#app')
|
使用
1
| <div v-waves="light"></div>
|
彩色流动边框

其实现原理是在一个盒子上叠加两个盒子,利用这两个盒子的 :before
、:after
给四条边添加渐变色的线。初始时四条线的位置如下图:

然后利用 animation
和 animation-delay
来实现流动的效果。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| <div class="box"> <div class="wrap1"></div> <div class="wrap2"></div> </div>
<style> .wrap1, .wrap2 { width: inherit; height: inherit; position: absolute; overflow: hidden; } .wrap1:before { content: ""; position: absolute; top: 0; width: 100%; height: 3px; z-index: 1; background: linear-gradient(90deg, transparent, #1095c1); animation: top 2s linear infinite; } .wrap1:after { content: ""; position: absolute; width: 3px; height: 100%; top: -100%; background: linear-gradient(180deg, transparent, #1095c1); filter: hue-rotate(60deg); right: 0; animation: right 2s linear infinite; animation-delay: 0.5s; } .wrap2:before { content: ""; position: absolute; width: 3px; height: 100%; top: 100%; background: linear-gradient(0deg, transparent, #1095c1); filter: hue-rotate(120deg); animation: left 2s linear infinite; animation-delay: 1.5s; } .wrap2:after { content: ""; position: absolute; width: 100%; height: 3px; background: linear-gradient(270deg, transparent, #1095c1); filter: hue-rotate(180deg); bottom: 0; left: 100%; animation: bottom 2s linear infinite; animation-delay: 1s; }
@keyframes top { 0% { left: -100%; } 50%, 100% { left: 100%; } } @keyframes right { 0% { top: -100%; } 50%, 100% { top: 100%; } } @keyframes bottom { 0% { left: 100%; } 50%, 100% { left: -100%; } } @keyframes left { 0% { top: 100%; } 50%, 100% { top: -100%; } } </style>
|
loading 展示
示例一

利用网格布局创建九个圆,分别为每个圆添加动画,使其透明度发生变化。对角线元素透明度的变化顺序可通过 delay 实现。animation-direction: alternate
起到关键作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <div class="loading-box"> <div v-for="_ in 9" class="loading-item"></div> </div>
<style> .loading-box { width: 200px; height: 200px; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); background-color: black; justify-items: center; align-items: center; } .loading-item { width: 20px; height: 20px; background-color: #3cefff; border-radius: 50%; animation: fade 1.5s ease-in-out infinite alternate; } .loading-item:nth-child(2), .loading-item:nth-child(4) { animation-delay: 0.25s; } .loading-item:nth-child(3), .loading-item:nth-child(5), .loading-item:nth-child(7) { animation-delay: 0.5s; } .loading-item:nth-child(6), .loading-item:nth-child(8) { animation-delay: 0.75s; } .loading-item:last-child { animation-delay: 1s; } @keyframes fade { 100% { opacity: 0.2; } } </style>
|
示例二

1 2 3 4 5 6 7 8 9
| <div style="width: 150px; height: 150px; display: flex; justify-content: center; align-items: center"> <svg> <circle cx="75" cy="75" r="50" fill="none" stroke="red" stroke-width="3" stroke-dasharray="1, 400" stroke-dashoffset="0" /> </svg> </div>
<style>
</style>
|