最近,群里在討論這么一個(gè)有趣的交互效果,來源于:v: 通過審查元素,發(fā)現(xiàn)原效果借助了 Canvas 實(shí)現(xiàn)。 思索了一番,覺得這個(gè)效果利用 CSS 配合部分 Javascript 代碼完全也是可以做到的。 于是動(dòng)手嘗試了一番,最終完美的復(fù)刻了該效果: 過程中還是有非常多有意思的技巧存在的,因此,本文將帶大家一起,從 0 到 1 實(shí)現(xiàn)這個(gè)有趣的交互效果。 利用混合模式實(shí)現(xiàn)疊加效果整個(gè)效果,比較核心的一塊便是當(dāng)鼠標(biāo) Hover 上去時(shí),整個(gè)元素疊加上一層黑色圖層,但是呈現(xiàn)了不一樣的疊加效果。 這個(gè)了解混合模式(mix-blend-mode )的同學(xué)應(yīng)該一下就能想到。 在之前,我們也有多篇文章講解過混合模式,感興趣的可以隨意快速瀏覽一下,下面是我寫過的 15 篇與混合模式相關(guān)的合集鏈接: 在這里,我們也快速過一下效果中需要用到的混合模式。 正常而言,假設(shè)我們有這么一個(gè) UI 效果: HTML復(fù)制代碼<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit.....</div> CSS復(fù)制代碼body { background: #eee;}div { width: 400px; background: #42b983; color: #fff; border: 3px solid #333; border-radius: 5px;} 效果如下: 我們利用 div 的偽元素,在其元素本身上疊加一個(gè)純白色塊: CSS復(fù)制代碼div::before { content: ""; position: absolute; inset: -10px; background: #fff; z-index: 1;} 正常而言,由于疊加了一個(gè)白色色塊在元素之上,肯定是什么都看不到了: 而 CSS 中,混合模式(mix-blend-mode )的作用,就是將多個(gè)圖層混合得到一個(gè)新的效果。 如果,我們給上述效果中的偽元素,添加一個(gè) mix-blend-mode: difference ,則會(huì)得到如下效果: CSS復(fù)制代碼div::before { content: ""; position: absolute; inset: -10px; background: #fff; z-index: 1; mix-blend-mode: difference;} 效果如下: 其中,混合模式 mix-blend-mode: difference 意為差值模式。該混合模式會(huì)查看每個(gè)通道中的顏色信息,比較底色和繪圖色,用較亮的像素點(diǎn)的像素值減去較暗的像素點(diǎn)的像素值。 與白色混合將使底色反相;與黑色混合則不產(chǎn)生變化。 通俗一點(diǎn)就是上方圖層的亮區(qū)將下方圖層的顏色進(jìn)行反相,暗區(qū)則將顏色正常顯示出來,效果與原圖像是完全相反的顏色。 并且,由于我們?cè)O(shè)置了 body 的顏色,所以在動(dòng)畫的一開始,偽元素白色的背景色與 body 的白色通過混合模式疊加直接變成了黑色。 實(shí)現(xiàn)鼠標(biāo) cursor 動(dòng)畫仔細(xì)看我們整體要實(shí)現(xiàn)的效果,其中鼠標(biāo)樣式與平常不太一樣: 接下來,我們就需要實(shí)現(xiàn)這么個(gè)效果,**把我們的 Curosr 鼠標(biāo)樣式,改成兩個(gè)小圓點(diǎn),并且外層圓點(diǎn)的運(yùn)動(dòng)帶一點(diǎn)延遲效果。 這個(gè)也好實(shí)現(xiàn),我們?cè)?有意思的鼠標(biāo)指針交互探究 中,有實(shí)現(xiàn)過一個(gè)類似的效果: 修改鼠標(biāo)樣式首先,第一個(gè)問題,我們可以看到,上圖中,鼠標(biāo)指針的樣式被修改成了一個(gè)圓點(diǎn): 正常而言應(yīng)該是這樣: 如何實(shí)現(xiàn)呢?原來在 CSS 中,我們可以通過 cursor 樣式,對(duì)鼠標(biāo)指針形狀進(jìn)行修改。 利用 cursor 修改鼠標(biāo)樣式cursor CSS 屬性設(shè)置鼠標(biāo)指針的類型,在鼠標(biāo)指針懸停在元素上時(shí)顯示相應(yīng)樣式。 CSS復(fù)制代碼cursor: auto;cursor: pointer;...cursor: zoom-out;/* 使用圖片 */cursor: url(hand.cur)/* 使用圖片,并且設(shè)置 fallback 兜底 */cursor: url(hand.cur), pointer; 比不過在這里,我們需要通過 cursor: none 隱藏頁面的鼠標(biāo)指針: CSS復(fù)制代碼{ cursor: none;} 如此一來,頁面上的鼠標(biāo)指針就消失了: 通過全局事件監(jiān)聽,模擬鼠標(biāo)指針既然,消失了,我們就簡(jiǎn)單模擬一個(gè)鼠標(biāo)指針。 我們首先實(shí)現(xiàn)一個(gè) 10px x 10px 的圓形 div,設(shè)置為基于 <body> 絕對(duì)定位: HTML復(fù)制代碼<div id="g-pointer"></div> CSS復(fù)制代碼#g-pointer { position: absolute; top: 0; left: 0; width: 10px; height: 10px; background: #000; border-radius: 50%;} 那么,在頁面上,我們就得到了一個(gè)圓形黑點(diǎn): 接著,通過事件監(jiān)聽,監(jiān)聽 body 上的 mousemove ,將小圓形的位置與實(shí)時(shí)鼠標(biāo)指針位置重合: Javascript復(fù)制代碼const element = document.getElementById("g-pointer");const body = document.querySelector("body");function setPosition(x, y) { element.style.transform = `translate(${x}px, ${y}px)`; }body.addEventListener('mousemove', (e) => { window.requestAnimationFrame(function(){ setPosition(e.clientX - 5, e.clientY - 5); });}); 這樣,如果不設(shè)置 cursor: none ,將會(huì)是這樣一個(gè)效果: 再給 body 加上 cursor: none ,就相當(dāng)于模擬了一個(gè)鼠標(biāo)指針: 在這個(gè)基礎(chǔ)上,由于現(xiàn)在的鼠標(biāo)指針,實(shí)際上是個(gè) div ,因此我們可以給它加上任意的交互效果。 好,我們把上述內(nèi)容無縫銜接到本效果中,并且,我們其實(shí)需要同時(shí)模擬兩個(gè)鼠標(biāo),并且讓第二個(gè)指針的動(dòng)畫,帶有一點(diǎn)延遲效果,完整的代碼: HTML復(fù)制代碼<div id="g-pointer-1"></div><div id="g-pointer-2"></div> CSS復(fù)制代碼#g-pointer-1,#g-pointer-2 { position: absolute; top: 0; left: 0; width: 12px; height: 12px; background: #999; border-radius: 50%; background-color: #4caf50;}#g-pointer-2 { width: 42px; height: 42px; background: #fff; transition: .15s ease-out;} Javascript復(fù)制代碼const body = document.querySelector("body");const element = document.getElementById("g-pointer-1");const element2 = document.getElementById("g-pointer-2");const halfAlementWidth = element.offsetWidth / 2;const halfAlementWidth2 = element2.offsetWidth / 2;body.addEventListener("mousemove", (e) => { window.requestAnimationFrame(function () { setPosition(e.clientX, e.clientY); });});function setPosition(x, y) { window.requestAnimationFrame(function () { element.style.transform = `translate(${x - halfAlementWidth}px, ${ y - halfAlementWidth }px)`; element2.style.transform = `translate(${x - halfAlementWidth2}px, ${ y - halfAlementWidth2 }px)`; });} 這樣,我們就完成了頁面鼠標(biāo)樣式的改造。不過,有一點(diǎn)需要注意的是,利用模擬的鼠標(biāo)指針去 Hover 元素,Click 元素的時(shí)候,會(huì)發(fā)現(xiàn)這些事件都無法觸發(fā)。 這是由于,此時(shí)被隱藏的指針下面,其實(shí)懸浮的我們模擬鼠標(biāo)指針,因此,所有的 Hover、Click 事件都觸發(fā)在了這個(gè)元素之上。 當(dāng)然,這個(gè)也非常好解決,我們只需要給模擬指針的元素,添加上 pointer-events: none ,阻止默認(rèn)的鼠標(biāo)事件,讓事件透?jìng)骷纯伞?/p> 同時(shí),我們也可以給這個(gè)模擬鼠標(biāo)元素,加上一個(gè)混合模式。如此一來,我們需要給兩個(gè)鼠標(biāo)元素,再加上兩個(gè)樣式: CSS復(fù)制代碼#g-pointer-1,#g-pointer-2 { // ... mix-blend-mode: exclusion; pointer-events: none;} 這樣,我們就成功地模擬了新的鼠標(biāo)樣式: 實(shí)現(xiàn)完整動(dòng)畫效果好,基于上述效果鋪墊,我們就只剩下一個(gè)任務(wù)了,如何在 Hover 元素的時(shí)候,將鼠標(biāo)樣式外圈,吸附到整個(gè)元素之上: 要完成這個(gè)動(dòng)畫,必須需要借助 Javascript,通過事件的一些回調(diào)完成,總體而言整體思路如下: 兩個(gè)模擬鼠標(biāo)指針的元素 #g-pointer-1 、#g-pointer-2 依舊如上面描述的那般,通過 <body> 的 mousemove 事件控制,不過在此過程中,額外需要知道是否經(jīng)過(Hover)了不同的元素 通過 mouseover 事件監(jiān)聽器,判斷當(dāng)前鼠標(biāo)是否懸停在我們需要進(jìn)行吸附擴(kuò)大動(dòng)畫的的元素上 通過 mouseout 事件,判斷鼠標(biāo)是否離開目標(biāo)元素 如果鼠標(biāo)懸停在目標(biāo)元素上,則計(jì)算當(dāng)前吸附的目標(biāo)元素的高寬、元素的 border-radius 及相對(duì)頁面右上角的坐標(biāo) 由于模擬的鼠標(biāo)元素,本身就是絕對(duì)定位,因此,可以通過第(3)步的計(jì)算,設(shè)置模擬的鼠標(biāo)元素新的高寬及絕對(duì)定位坐標(biāo),并且其坐標(biāo)不再隨鼠標(biāo)指針的變化而變化 只有當(dāng)鼠標(biāo)指針離開目標(biāo)元素,才復(fù)原模擬的鼠標(biāo)元素的大小,并且讓其重新跟隨鼠標(biāo)的移動(dòng)而移動(dòng)
本質(zhì)上而言,通過一句話概括,在整個(gè)鼠標(biāo)元素移動(dòng)的過程中,如果有懸停到任一元素上,則將外圈鼠標(biāo)元素 #g-pointer-2 的大小及坐標(biāo)更改,通過元素的高寬及 border-radius 變化實(shí)現(xiàn)視覺上的放大、縮小動(dòng)畫。 首先,通過 mouseover 和 mouseout ,我們可以得知我們的鼠標(biāo)元素,是否懸停在某些特定元素之上,譬如帶有 .g-animation 的元素: HTML復(fù)制代碼<div class="g-animation">Lorem ...</div>// 模擬鼠標(biāo)指針的兩個(gè)元素<div id="g-pointer-1"></div><div id="g-pointer-2"></div> javascript復(fù)制代碼window.addEventListener("mouseover", (event) => { const target = event.target; if (target.classList.contains("g-animation")) { console.log('mouseover'); }});window.addEventListener("mouseout", (event) => { const target = event.target; if (target.classList.contains("g-animation")) { console.log('mouseout'); }});// 剩余的模擬鼠標(biāo)移動(dòng)的 JavaScript 代碼// ... 這樣就能準(zhǔn)確知道元素是否懸停在某個(gè)目標(biāo)元素之上: 利用這兩種狀態(tài),我們就可以繼續(xù)實(shí)現(xiàn)剩余的放大吸附動(dòng)畫。 而放大吸附動(dòng)畫其實(shí)也很簡(jiǎn)單,其核心就是在 mouseover 時(shí),計(jì)算出目標(biāo)元素的坐標(biāo)及高寬,再設(shè)置需要放大的外圈鼠標(biāo)元素的新的 width 、height 、border-radius 、transform 。同時(shí),讓其不再跟隨真實(shí)的鼠標(biāo)運(yùn)動(dòng)而運(yùn)動(dòng)。 在 mouseout 時(shí),復(fù)原外圈鼠標(biāo)元素的大小及恢復(fù)其跟隨真實(shí)的鼠標(biāo)運(yùn)動(dòng)而運(yùn)動(dòng)。 如此一來,整個(gè)效果的完整的代碼如下: HTML復(fù)制代碼// 代表了頁面不同的可以吸附的元素,它們的高寬、border-radius 各不相同<div class="g-animation">Lorem ...</div><div class="g-animation">Lorem ...</div><div class="g-animation">Lorem ...</div>// 模擬鼠標(biāo)指針的兩個(gè)元素<div id="g-pointer-1"></div><div id="g-pointer-2"></div> CSS復(fù)制代碼body { background: #fff; cursor: none;}#g-pointer-1,#g-pointer-2{ position: absolute; top: 0; left: 0; width: 12px; height: 12px; background: #999; border-radius: 50%; background-color: #4caf50; z-index: 1; mix-blend-mode: exclusion; pointer-events: none;}#g-pointer-2 { width: 42px; height: 42px; background: #fff; transition: .15s ease-out;} Javascript復(fù)制代碼const body = document.querySelector("body");const element = document.getElementById("g-pointer-1");const element2 = document.getElementById("g-pointer-2");const halfAlementWidth = element.offsetWidth / 2;const halfAlementWidth2 = element2.offsetWidth / 2;// 該變量用于跟蹤鼠標(biāo)是否懸停在具有類名為 .g-animation 的元素上let isHovering = false;// 判斷元素是否懸停在具有類名為 .g-animation 的元素上window.addEventListener("mouseover", (event) => { const target = event.target; if (target.classList.contains("g-animation")) { isHovering = true; const rect = target.getBoundingClientRect(); const style = window.getComputedStyle(target); element2.style.width = `${rect.width + 20}px`; element2.style.height = `${rect.height + 20}px`; element2.style.borderRadius = `${style.borderRadius}`; element2.style.transform = `translate(${rect.left - 10}px, ${ rect.top - 10 }px)`; }});// 判斷元素是否離開在具有類名為 .g-animation 的元素上window.addEventListener("mouseout", (event) => { const target = event.target; if (target.classList.contains("g-animation")) { isHovering = false; // 樣式復(fù)原 element2.style.width = `42px`; element2.style.height = `42px`; element2.style.borderRadius = `50%`; }});// 用于控制兩個(gè)鼠標(biāo)指針元素body.addEventListener("mousemove", (e) => { window.requestAnimationFrame(function () { setPosition(e.clientX, e.clientY); });});function setPosition(x, y) { window.requestAnimationFrame(function () { element.style.transform = `translate(${x - halfAlementWidth}px, ${ y - halfAlementWidth }px)`; if (!isHovering) { element2.style.transform = `translate(${x - halfAlementWidth2}px, ${ y - halfAlementWidth2 }px)`; } });} 如此一來,頁面上任意帶有 .g-animation 的元素,都可以允許模擬鼠標(biāo)的元素進(jìn)行吸附動(dòng)畫。 我們也就實(shí)現(xiàn)了文章最開頭的動(dòng)畫效果: 完整的代碼很少,你可以戳這里看完整的代碼及效果展示:CodePen Demo -- Cursor Hover Animation Demo 鏈接:https:///post/7347626854617202724
|