【動畫進階】神奇的 3D 卡片反光閃爍動效

最近,有羣裏在羣裏發了這麼一個非常有意思的卡片 Hover 動效,來源於此網站 -- key-drop,效果如下:

非常有意思酷炫的效果。而本文,我們不會完全還原此效果,而是基於此效果,嘗試去製作這麼一個類似的卡片交互效果:

該效果的幾個核心點:

  1. 卡片的 3D 旋轉跟隨鼠標移動效果
  2. 如何讓卡片在 Hover 狀態,有不同的光澤變化
  3. 如何讓卡片在 Hover 狀態,有 Blink,Blink 的星星閃爍效果

當然,要做到卡片的 3D 旋轉跟隨鼠標移動效果需要一定程度的藉助 JavaScript,因此,最終的效果是 CSS 配合 JavaScript 以及一些動態效果的 Gif 共同實現。

好,下面就讓我們一步一步一起來實現這個效果。

卡片的 3D 旋轉跟隨效果

OK,接下來,如何實現 3D 卡片效果呢?

這個效果之前在 讓交互更加生動!有意思的鼠標跟隨 3D 旋轉動效 實現過一次,我們複習一下。

這個交互效果主要有兩個核心:

  1. 藉助了 CSS 3D 的能力
  2. 元素的旋轉需要和鼠標的移動相結合

我們的目標是實現這樣一個動畫效果:

這裏,我們其實有兩個核心元素:

  1. 鼠標活動區域
  2. 旋轉物體本身

鼠標在鼠標活動區域內的移動,會影響旋轉物體本身的 3D 旋轉,而旋轉的方向其實可以被分解爲 X 軸方向與 Y 軸方向。

我們來看一下,假設我們的 HTML 結構如下:

<body>
    <div id="element"></div>
</body>

得到這樣一個圖形:

這裏,body 的範圍就是整個鼠標可活動區域,也是我們添加鼠標的 mousemove 事件的宿主 target,而 #element 就是需要跟隨鼠標一起轉動的旋轉物體本身。

因爲整個效果是需要基於 CSS 3D 的,我們首先加上簡單的 CSS 3D 效果:

body {
    width: 100vw;
    height: 100vh;
    transform-style: preserve-3d;
    perspective: 500px;
}

div {
    width: 200px;
    height: 200px;
    background: #000;
    transform-style: preserve-3d;
}

效果如下:

沒有什麼不一樣。這是因爲還沒有添加任何的 3D 變換,我們給元素添加 X、Y 兩個方向的 rotate() 試一下(注意,這裏默認的旋轉圓心即是元素中心):

div {
     transform: rotateX(15deg) rotateY(30deg);
}

效果如下,是有那麼點意思了:

好,接下來,我們的目標就是通過結合 mouseover 事件,讓元素動起來。

控制 X 方向的移動

當然,爲了更加容易理解,我們把動畫拆分爲 X、Y 兩個方向上的移動。首先看 X 方向上的移動:

這裏,我們需要以元素的中心爲界:

  1. 當鼠標在中心右側連續移動,元素繞 Y 軸移動,並且值從 0 開始,越來越大,範圍爲(0, +∞)deg
  2. 反之,當鼠標在中心左側連續移動,元素繞 Y 軸移動,並且值從 0 開始,越來越小,範圍爲(-∞, 0)deg

這樣,我們可以得到這樣一個公式:

rotateY = (鼠標 x 座標 - 元素左上角 x 座標 - 元素寬度的一半)deg

通過綁定 onmousemove 事件,我們嘗試一下:

const mouseOverContainer = document.getElementsByTagName("body")[0];
const element = document.getElementById("element");

mouseOverContainer.onmousemove = function(e) {
  let box = element.getBoundingClientRect();
  let calcY = e.clientX - box.x - (box.width / 2);
    
  element.style.transform  = "rotateY(" + calcY + "deg) ";
}

效果如下:

好吧,旋轉的太誇張了,因此,我們需要加一個倍數進行控制:

const multiple = 20;
const mouseOverContainer = document.getElementsByTagName("body")[0];
const element = document.getElementById("element");

mouseOverContainer.onmousemove = function(e) {
  let box = element.getBoundingClientRect();
  let calcY = (e.clientX - box.x - (box.width / 2)) / multiple;
    
  element.style.transform  = "rotateY(" + calcY + "deg) ";
}

通過一個倍數約束後,效果好了不少:

控制 Y 方向的移動

同理,我們利用上述的方式,同樣可以控制 Y 方向上的移動:

const multiple = 20;
const mouseOverContainer = document.getElementsByTagName("body")[0];
const element = document.getElementById("element");

mouseOverContainer.onmousemove = function(e) {
  let box = element.getBoundingClientRect();
  let calcX = (e.clientY - box.y - (box.height / 2)) / multiple;
    
  element.style.transform  = "rotateX(" + calcX + "deg) ";
};

效果如下:

當然,在這裏,我們會發現方向是元素運動的方向是反的,所以需要做一下取反處理,修改下 calcX 的值,乘以一個 -1 即可:

let calcX = (e.clientY - box.y - (box.height / 2)) / multiple * -1;

結合 X、Y 方向的移動

OK,到這裏,我們只需要把上述的結果合併一下即可,同時,上面我們使用的是 onmousemove 觸發每一次動畫移動。現代 Web 動畫中,我們更傾向於使用 requestAnimationFrame 去優化我們的動畫,確保每一幀渲染一次動畫即可。

完整的改造後的代碼如下:

const multiple = 20;
const mouseOverContainer = document.getElementsByTagName("body")[0];
const element = document.getElementById("element");

function transformElement(x, y) {
  let box = element.getBoundingClientRect();
  let calcX = -(y - box.y - (box.height / 2)) / multiple;
  let calcY = (x - box.x - (box.width / 2)) / multiple;
  
  element.style.transform  = "rotateX("+ calcX +"deg) "
                        + "rotateY("+ calcY +"deg)";
}

mouseOverContainer.addEventListener('mousemove', (e) => {
  window.requestAnimationFrame(function(){
    transformElement(e.clientX, e.clientY);
  });
});

至此,我們就能簡單的實現鼠標跟隨 3D 旋轉動效:

設置平滑出入

現在,還有最後一個問題,就是當我們的鼠標離開活動區域時,元素的 transform 將停留在最後一幀,正確的表現應該是復原到原狀。因此,我們還需要添加一些事件監聽做到元素的平滑復位。

通過一個 mouseleave 事件配合元素的 transition 即可。

div {
    // 與上述保持一致...
    transition: all .2s;
}
mouseOverContainer.addEventListener('mouseleave', (e) => {
  window.requestAnimationFrame(function(){
    element.style.transform = "rotateX(0) rotateY(0)";
  });
});

至此,我們就可以完美的實現平滑出入,整體效果最終如下:

完整的代碼,你可以戳這裏:CodePen Demo -- CSS 3D Rotate With Mouse Move

Hover 狀態下的光澤變化

好,有了上述鋪墊之後,我們就可以將黑色背景圖,替換成實際的圖片,得到這麼一個初步效果:

接下來,我們需要讓卡片能夠變得有光澤,並且也能基於鼠標 Hover 的座標不同,展現出不一樣的效果,像是這樣:

怎麼實現呢?看似複雜,其實只需要簡單的利用混合模式即可。其中本質就是圖片疊加上黑白相間的漸變,再調整混合模式,就能實現上述的高光效果。

代碼如下:

<div></div>
div {
    position: relative;
    background: url('image.png');
    
    &::before {
        content: "";
        position: absolute;
        inset: 0;
        background: 
            linear-gradient(
                115deg, 
                transparent 0%, 
                rgba(255, 255, 255, 0.5 30%), 
                rgba(0, 0, 0, .5) 55%), 
                rgba(255, 255, 255, .5) 80%), 
                transparent 100%
            );
        mix-blend-mode: color-dodge;
    }
}

這裏,我們利用 div 元素的背景展示了圖片,利用元素的僞元素展示了黑白漸變效果,最終再疊加上混合模式 mix-blend-mode: color-dodge,示意圖如下:

但是,此時,只有卡片是有 3D 效果的,疊加的黑白漸變層是不會隨着 Hover 效果進行變化的:

爲了解決這個問題,我們需要讓漸變圖層也能受到 Hover 的動態影響,這個好做,我們額外引入一個 CSS 變量,基於鼠標當前 Hover 卡片時,距離卡片最左側的橫向距離,設置動態的 CSS 變量。

改造一下代碼:

<div id="g-img"></div>
div {
    --per: 30%;
    position: relative;
     // ...
    
    &::before {
        content: "";
        position: absolute;
        inset: 0;
        background: 
            linear-gradient(
                115deg, 
                transparent 0%, 
                rgba(255, 255, 255, 0.5) var(--per), 
                rgba(0, 0, 0, .5) calc(var(--per) + 25%), 
                rgba(255, 255, 255, .5) calc(var(--per) + 50%), 
                transparent 100%
            );
        mix-blend-mode: color-dodge;
    }
}
const multiple = 15;
const mouseOverContainer = document.getElementsByTagName("body")[0];
const element = document.getElementById("element");
const img = document.getElementById("g-img");

function transformElement(x, y) {
    let box = element.getBoundingClientRect();
    const calcX = -(y - box.y - box.height / 2) / multiple;
    const calcY = (x - box.x - box.width / 2) / multiple;
    const percentage = parseInt((x - box.x) / box.width * 1000) / 10;
    
    element.style.transform = "rotateX(" + calcX + "deg) " + "rotateY(" + calcY + "deg)";

    // 額外增加一個控制 --per 的變量寫入
    img.style = `--per: ${percentage}%`;
}

mouseOverContainer.addEventListener("mousemove", (e) => {
    window.requestAnimationFrame(function () {
        transformElement(e.clientX, e.clientY);
    });
});

簡單解釋一下,上述代碼最核心的部分就是引入了 --per CSS 變量,其應用在漸變代碼中。

我們通過計算當前鼠標距離卡片左側的橫向距離,除以卡片整體的寬度,得到 --per 實際表示的百分比,再賦值給 --per,以此實現 Hover 時候的光效變化:

疊加星星閃爍效果

好,效果已經非常接近了。當然,總感覺缺少什麼,我們可以在這一步,繼續疊加上另外一層星星閃爍的效果。

這裏,我們可以用現成的 GIF 圖,像是這樣(圖片來源於 Pokemon Card Holo Effect):

這樣,我們的整個效果,其實就變成了這種疊加狀態:

我們再簡單改造一下代碼:

#g-img {
    --per: 30%;
    position: relative;
    background: url('image.png');
    
    &::after {
        content: "";
        display: none;
        position: absolute;
        inset: 0;
        background: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/13471/sparkles.gif");
        mix-blend-mode: color-dodge;
    }
    
    &::before {
        content: "";
        display: none;
        position: absolute;
        background: 
            linear-gradient(
                115deg, 
                transparent 0%, 
                rgba(255, 255, 255, 0.7) var(--per), 
                rgba(0, 0, 0, .6) calc(var(--per) + 25%), 
                rgba(255, 255, 255, .5) calc(var(--per) + 50%), 
                transparent 100%
            );
        mix-blend-mode: color-dodge;
    }
    
    &:hover::after,
    &:hover::before {
        display: block;
    }
}

當 Hover 狀態下,才展示漸變背景與星星 Gif 圖的疊加效果,最終,我們就實現了最開頭的效果:

完整的代碼,你可以戳這裏 CodePen Demo -- CSS 3D Rotate With Mouse Move

嘗試不同漸變背景與不同混合模式

瞭解上述製作方式的全過程後,我們就可以改變疊加的混合模式與漸變背景,以創造更多不一樣的效果。

像是這樣:

完整的代碼,你可以戳這裏 CodePen Demo -- CSS 3D Rotate With Mouse Move2

或者是這樣:

完整的代碼,你可以戳這裏 CodePen Demo -- CSS 3D Rotate With Mouse Move3

最後

怎樣,學會了嗎。通過不同的混合模式與不同的漸變背景,可以排列組合出非常多種有趣有意思的效果。感興趣的,一定動手試試!

好了,本文到此結束,希望本文對你有所幫助 😃

更多精彩 CSS 技術文章彙總在我的 Github -- iCSS ,持續更新,歡迎點個 star 訂閱收藏。

如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章