<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
不知道你們有沒有使用過 Material UI。這是一個 React UI 元件庫,它實現了 Google 的 Material Design。
Material Design 設計規範中包含了很多關於點選的漣漪效果,類似於一塊石頭跌落水中所產生的波浪效果。
以下是效果圖
速度放慢之後的效果:
我們把 overflow: hidden
去掉之後:
本文就以 Material Design 中的漣漪效果作為目標,來使用原生的 JavaScript、CSS、HTML 來實現此效果。
通過觀察,我們可以發現點選的漣漪效果是在滑鼠點選的點開始以一個正圓往外擴散。當圓形擴散到正好能將 Button 全部包圍住的時候停止,在擴散的過程中顏色逐漸變淺直到消失,並且此效果可以疊加。
長按效果也是一個圓往外擴散,只不過是在長按結束之後,圓才會消失。
除了滑鼠點選效果外,還有鍵盤焦點事件的效果。當使用鍵盤的 Tab 鍵切換到按鈕上的時候,會有一個抖動的效果,類似於呼吸效果。
我們提煉出幾個比較關鍵的點:
第一點,我們可以通過 JavaScript 計算當前滑鼠的座標資訊;第三點,獲取 Button 四個頂點的座標資訊,再選一個距離滑鼠最遠的點,以它們的距離作為半徑來畫一個圓;第五點,每一個效果都是一個 dom 元素,每點選一次就追加一個 dom 元素,在動畫結束的時候,移除此 dom;
建立一個 index.html 檔案,包含以下內容;
<!-- 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>
建立 button.css 和 ripple.css,分別是 Button 的基礎樣式和漣漪效果的樣式。
/* 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。建立一個 ripple.apis.js 檔案,編寫 startRipple
函數。該函數首先要獲取 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) { // ... // 效果開始的座標(相對於 Button) let rippleX, rippleY // 滑鼠當前的座標 let clientX = 0, clientY = 0 /** * 漣漪效果是否從節點的中心擴散,否則從滑鼠點選的位置開始擴散 * 使用 Tab 鍵移動焦點的時候,從節點的中心擴散 */ 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 }
通過勾股定理,構造一個能正好包圍當前元素的圓。
// ripple.apis.js export function startRipple(event) { // ... // 從滑鼠點選的中心位置,構造一個能正好包圍當前元素的圓 const sizeX = Math.max(width - rippleX, rippleX) * 2 const sizeY = Math.max(height - rippleY, rippleY) * 2 const diagonal = Math.sqrt(sizeX ** 2 + sizeY ** 2) }
再建立一個 createRippleChild
函數,用來建立漣漪效果的 DOM,並且使用一個全域性變數來儲存已經建立的 DOM。
// ripple.apis.js const rippleChildren = [] /** * 建立以下結構並返回: * <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
函數,使用剛才建立的 createRippleChild
函數。
// 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
函數之後,我們再建立一個 stopRipple
函數。該函數中,從 rippleChildren
取出最早建立的 DOM,新增一個動畫結束的監聽事件,在動畫結束的時候,刪除該 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') }
此時,我們已經完成了大部分的程式碼,接下來就是給 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 中編輯
到此這篇關於使用JavaScript實現按鈕的漣漪效果的文章就介紹到這了,更多相關js實現按鈕漣漪效果內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45