使用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)最遠的點,以它們的距離作為半徑來畫一個圓;第五點,每一個效果都是一個 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-01Bootstrap輪播插件中圖片變形的終極解決方案 使用jqthumb.js
這篇文章主要介紹了Bootstrap輪播插件中圖片變形的終極解決方案,使用jqthumb.js,感興趣的小伙伴們可以參考一下2016-07-07js數(shù)組常見操作及數(shù)組與字符串相互轉(zhuǎn)化實例詳解
這篇文章主要介紹了js數(shù)組常見操作及數(shù)組與字符串相互轉(zhuǎn)化方法,以實例形式較為詳細的分析并總結(jié)了JavaScript數(shù)組的常見使用技巧與轉(zhuǎn)化方法,需要的朋友可以參考下2015-11-11