使用JavaScript實現(xiàn)按鈕的漣漪效果實例代碼
前言
不知道你們有沒有使用過 Material UI。這是一個 React UI 組件庫,它實現(xiàn)了 Google 的 Material Design。
Material Design 設(shè)計規(guī)范中包含了很多關(guān)于點擊的漣漪效果,類似于一塊石頭跌落水中所產(chǎn)生的波浪效果。
以下是效果圖



速度放慢之后的效果:

我們把 overflow: hidden 去掉之后:

本文就以 Material Design 中的漣漪效果作為目標(biāo),來使用原生的 JavaScript、CSS、HTML 來實現(xiàn)此效果。
分析
通過觀察,我們可以發(fā)現(xiàn)點擊的漣漪效果是在鼠標(biāo)點擊的點開始以一個正圓往外擴散。當(dāng)圓形擴散到正好能將 Button 全部包圍住的時候停止,在擴散的過程中顏色逐漸變淺直到消失,并且此效果可以疊加。
長按效果也是一個圓往外擴散,只不過是在長按結(jié)束之后,圓才會消失。
除了鼠標(biāo)點擊效果外,還有鍵盤焦點事件的效果。當(dāng)使用鍵盤的 Tab 鍵切換到按鈕上的時候,會有一個抖動的效果,類似于呼吸效果。
我們提煉出幾個比較關(guān)鍵的點:
- 從鼠標(biāo)點擊的位置開始擴散;
- 是一個正圓形;
- 圓形擴散到正好能將 Button 全部包圍住的時候停止;
- 長按效果;
- 效果疊加;
第一點,我們可以通過 JavaScript 計算當(dāng)前鼠標(biāo)的坐標(biāo)信息;第三點,獲取 Button 四個頂點的坐標(biāo)信息,再選一個距離鼠標(biāo)最遠(yuǎn)的點,以它們的距離作為半徑來畫一個圓;第五點,每一個效果都是一個 dom 元素,每點擊一次就追加一個 dom 元素,在動畫結(jié)束的時候,移除此 dom;
實現(xiàn)
創(chuàng)建一個 index.html 文件,包含以下內(nèi)容;
<!-- index.html --> <style> @import 'button.css'; @import 'ripple.css'; </style> <button class="button-root" id="ripple-example-button" type="button"> Button <!-- 用來裝漣漪效果的 DOM 的容器 --> <span class="ripple-root"></span> </button>
創(chuàng)建 button.css 和 ripple.css,分別是 Button 的基礎(chǔ)樣式和漣漪效果的樣式。
/* button.css */
.button-root {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 6px 16px;
font-size: 0.875rem;
font-weight: 500;
line-height: 1.75;
min-width: 64px;
margin: 0;
border-radius: 4px;
border: 1px solid rgba(25, 118, 210, 0.5);
cursor: pointer;
box-sizing: border-box;
outline: none;
appearance: none;
user-select: none;
color: #1976d2;
background-color: transparent;
transition-property: background-color, color, box-shadow, border-color;
transition-duration: 0.25s;
}
.button-root:hover {
background-color: rgba(25, 118, 210, 0.04);
border: 1px solid #1976d2;
}/* ripple.css */
@keyframes enterKeyframe {
0% {
transform: scale(0);
opacity: 0.1;
}
100% {
transform: scale(1);
opacity: 0.3;
}
}
@keyframes exitKeyframe {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes pulsateKeyframe {
0% {
transform: scale(0.9);
}
50% {
transform: scale(0.8);
}
100% {
transform: scale(0.9);
}
}
.ripple-root {
display: block;
position: absolute;
overflow: hidden;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
background-color: transparent;
z-index: 0;
border-radius: inherit;
}
.ripple-root > .ripple-child {
position: absolute;
display: block;
opacity: 0;
}
.ripple-root > .ripple-child.enter {
opacity: 0.3;
transform: scale(1);
animation: enterKeyframe 550ms ease-in-out;
}
.ripple-root > .ripple-child > .ripple-child-child {
opacity: 1;
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: currentColor;
}
.ripple-root > .ripple-child.exit > .ripple-child-child {
opacity: 0;
animation: exitKeyframe 550ms ease-in-out;
}
.ripple-root > .ripple-child.pulsate > .ripple-child-child {
position: absolute;
left: 0;
top: 0;
animation: pulsateKeyframe 2500ms ease-in-out 200ms infinite;
}開始寫 JavaScript。創(chuàng)建一個 ripple.apis.js 文件,編寫 startRipple 函數(shù)。該函數(shù)首先要獲取 Button 的位置信息和寬高。
// ripple.apis.js
export function startRipple(event) {
const { currentTarget: container } = event
const { left, top, width, height } = container.getBoundingClientRect()
}接著計算開始擴散的位置。
// ripple.apis.js
export function startRipple(event) {
// ...
// 效果開始的坐標(biāo)(相對于 Button)
let rippleX, rippleY
// 鼠標(biāo)當(dāng)前的坐標(biāo)
let clientX = 0, clientY = 0
/**
* 漣漪效果是否從節(jié)點的中心擴散,否則從鼠標(biāo)點擊的位置開始擴散
* 使用 Tab 鍵移動焦點的時候,從節(jié)點的中心擴散
*/
let center = false
let isFocusVisible = false
if (container.matches(':focus-visible')) {
center = isFocusVisible = true
} else {
clientX = event.clientX
clientY = event.clientY
}
rippleX = center ? width / 2 : clientX - left
rippleY = center ? height / 2 : clientY - top
}通過勾股定理,構(gòu)造一個能正好包圍當(dāng)前元素的圓。
// ripple.apis.js
export function startRipple(event) {
// ...
// 從鼠標(biāo)點擊的中心位置,構(gòu)造一個能正好包圍當(dāng)前元素的圓
const sizeX = Math.max(width - rippleX, rippleX) * 2
const sizeY = Math.max(height - rippleY, rippleY) * 2
const diagonal = Math.sqrt(sizeX ** 2 + sizeY ** 2)
}再創(chuàng)建一個 createRippleChild 函數(shù),用來創(chuàng)建漣漪效果的 DOM,并且使用一個全局變量來保存已經(jīng)創(chuàng)建的 DOM。
// ripple.apis.js
const rippleChildren = []
/**
* 創(chuàng)建以下結(jié)構(gòu)并返回:
* <span class="ripple-child enter">
* <span class="ripple-child-child"></span>
* </span>
*/
function createRippleChild(rect) {
const rippleChild = document.createElement('span')
rippleChild.classList.add('ripple-child', 'enter')
const rippleChildChild = document.createElement('span')
rippleChildChild.classList.add('ripple-child-child')
rippleChild.appendChild(rippleChildChild)
const { height, left, top, width } = rect
rippleChild.style.height = height
rippleChild.style.width = width
rippleChild.style.top = top
rippleChild.style.left = left
rippleChildren.push(rippleChild)
return rippleChild
}回到 startRipple 函數(shù),使用剛才創(chuàng)建的 createRippleChild 函數(shù)。
// ripple.apis.js
export function startRipple(event) {
// ...
const rippleChild = createRippleChild({
width: `${diagonal}px`,
height: `${diagonal}px`,
left: `${-diagonal / 2 + rippleX}px`,
top: `${-diagonal / 2 + rippleY}px`,
})
if (isFocusVisible) {
rippleChild.classList.add('pulsate')
}
const rippleRoot = container.querySelector(':scope > .ripple-root')
rippleRoot.appendChild(rippleChild)
}完成了 startRipple 函數(shù)之后,我們再創(chuàng)建一個 stopRipple 函數(shù)。該函數(shù)中,從 rippleChildren 取出最早創(chuàng)建的 DOM,添加一個動畫結(jié)束的監(jiān)聽事件,在動畫結(jié)束的時候,刪除該 DOM。
// ripple.apis.js
export function stopRipple() {
const rippleChild = rippleChildren.shift()
if (!rippleChild) return
rippleChild.addEventListener('animationend', (event) => {
if (event.animationName === 'exitKeyframe') {
rippleChild.remove()
}
})
rippleChild.classList.add('exit')
}此時,我們已經(jīng)完成了大部分的代碼,接下來就是給 Button 綁定事件的時候了。在 index.html 文件中添加以下代碼:
<!-- index.html -->
<style>
@import 'button.css';
@import 'ripple.css';
</style>
<script type="module">
import { startRipple, stopRipple } from 'ripple.apis.js'
const button = document.querySelector('#ripple-example-button')
button.addEventListener('mousedown', startRipple)
button.addEventListener('focus', startRipple)
button.addEventListener('mouseup', stopRipple)
button.addEventListener('mouseleave', stopRipple)
button.addEventListener('blur', stopRipple)
</script>
<button class="button-root" id="ripple-example-button" type="button">
Button
<!-- 用來裝漣漪效果的 DOM 的容器 -->
<span class="ripple-root"></span>
</button>我們完成了所有的功能!完整的代碼在此倉庫中。
也可以直接在 CodeSandbox 中編輯
總結(jié)
到此這篇關(guān)于使用JavaScript實現(xiàn)按鈕的漣漪效果的文章就介紹到這了,更多相關(guān)js實現(xiàn)按鈕漣漪效果內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS實現(xiàn)的獲取銀行卡號歸屬地及銀行卡類型操作示例
這篇文章主要介紹了JS實現(xiàn)的獲取銀行卡號歸屬地及銀行卡類型操作,結(jié)合實例形式分析了javascript不依賴第三方接口計算銀行卡歸屬地相關(guān)信息操作技巧,需要的朋友可以參考下2019-01-01
Bootstrap輪播插件中圖片變形的終極解決方案 使用jqthumb.js
這篇文章主要介紹了Bootstrap輪播插件中圖片變形的終極解決方案,使用jqthumb.js,感興趣的小伙伴們可以參考一下2016-07-07
js數(shù)組常見操作及數(shù)組與字符串相互轉(zhuǎn)化實例詳解
這篇文章主要介紹了js數(shù)組常見操作及數(shù)組與字符串相互轉(zhuǎn)化方法,以實例形式較為詳細(xì)的分析并總結(jié)了JavaScript數(shù)組的常見使用技巧與轉(zhuǎn)化方法,需要的朋友可以參考下2015-11-11

