Jquery插件之hover動(dòng)畫

前言

前一陣子網(wǎng)上看到一個(gè)寫的挺好的動(dòng)畫插件,于是下載了下來。這兩天閱讀了一下代碼,然后決定分享一下原作者的思路。原效果鏈接找了半天找不到了,如果有看客留意到了可以留言,我附上鏈接(混淆后叫xs.js,可以在后面的github鏈接上下載)。

第一個(gè)效果

我在這里為了加深印象重敲了一遍,摘取部分有代表性的效果來講,首先我們看第一個(gè)效果圖:

第一個(gè)效果

我們所期望的是通過插件注冊(cè)一下容器即可產(chǎn)生效果,且插件擁有很多動(dòng)畫種類,首先是頁面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        a {
            color: #999;
            text-decoration: none;
        }
        * {
            margin: 0;
            padding: 0;
        }
        body {
            background-color: #222222;
        }
        .box {
            width: 1000px;
            display: flex;
            flex-wrap: wrap;
            margin: 20px auto;
        }
        .box li {
            width: 200px;
            height: 100px;
            background: #fff;
            line-height: 100px;
            margin: 20px;
            text-align: center;
            list-style: none;
        }
        .box1 {
            color: blue;
        }
    </style>
    <script type="text/javascript" src="jQuery-2.2.4.min.js"></script>
    <script type="text/javascript" src="btn-ani.js"></script>
    <script>
        $(document).ready(function () {
            $(".box1").btnHoverAni(1);
        });
    </script>
</head>
<body>
<div class="content">
    <ul class="box">
        <li class="box1"><a href="javascript:;">效果1</a></li>
    </ul>
</div>
</body>
</html>

然后是插件代碼,即上面引用的btn-ani.js,注釋我添加的應(yīng)該算詳細(xì)了

(function () {
    function ButtonHoverAnimation(btn, index, defaults) {
        //調(diào)用者
        this.btn = btn;
        //調(diào)用索引,哪一個(gè)效果
        this.index = index;
        this.color = defaults.color;
        this.init();
    }

    ButtonHoverAnimation.prototype = {
        init: function () {
            //后面的方法都使用這個(gè)obj對(duì)象的參數(shù)值
            var obj = this;
            obj.run = false;
            if (obj.btn.css('position') != 'fixed' && obj.btn.css('position') != 'absolute') {
                obj.btn.css('position', 'relative');
            }
            //寬高即調(diào)用者的寬高
            obj.width = obj.btn.width();
            obj.height = obj.btn.height();
            //將調(diào)用者的子內(nèi)容層級(jí)提高,并且在最底層添加一個(gè)與調(diào)用者等寬高的畫布canvas
            //這樣,我們的在canvas上的繪制作為背景不會(huì)影響到內(nèi)容的展示
            obj.btn.children().each(function () {
                if ($(this).css('position') != 'fixed' && $(this).css('position') != 'absolute') {
                    $(this).css({'position': 'relative', 'z-index': '2'});
                } else if (parseInt($(this).css('z-index')) < 2) {
                    $(this).css({'z-index': '2'});
                }
            });
            //背景色默認(rèn)白色
            if (obj.btn.css('background-color') != "rgba(0, 0, 0, 0)") {
                obj.backgroundColor = obj.btn.css('background-color');
            } else {
                obj.backgroundColor = '#ffffff';
            }
            //添加畫布,層級(jí)最低
            obj.btn.append('<canvas width="' + obj.width + '" height="' + obj.height + '" style="position:absolute; top:0; left:0; z-index:1;"></canvas>');
            obj.ctx = obj.btn.children('canvas')[0].getContext('2d');
            //畫筆的顏色,如果調(diào)用者沒有傳入,每次鼠標(biāo)移入初始化一個(gè)隨機(jī)色
            if (obj.color === false) {
                obj.btn.mouseenter(function () {
                    obj.color = 'hsl(' + (Math.random() * 360) + ',60%,80%)';
                    obj.ctx.fillStyle = obj.color;
                });
            } else {
                obj.ctx.fillStyle = obj.color;
            }
            //鼠標(biāo)移動(dòng)時(shí)記錄坐標(biāo)x,y
            obj.btn.mousemove(function (e) {
                obj.x = e.pageX - obj.btn.offset().left - parseInt(obj.btn.css('border-left-width'));
                obj.y = e.pageY - obj.btn.offset().top - parseInt(obj.btn.css('border-top-width'));
            });
            obj.array = [];
            //鼠標(biāo)移入移出時(shí),我們記錄一個(gè)mouseenter變量,并且移入時(shí)使用display方法去尋找對(duì)應(yīng)的效果
            obj.btn.mouseenter(function (e) {
                obj.mouseenter = true;
                obj.x = e.pageX - obj.btn.offset().left - parseInt(obj.btn.css('border-left-width'));
                obj.y = e.pageY - obj.btn.offset().top - parseInt(obj.btn.css('border-top-width'));
                obj.display(obj);
            });
            obj.btn.mouseleave(function () {
                obj.mouseenter = false;
            });
            //清屏
            obj.ctx.clearRect(0, 0, obj.width, obj.height);
        },
            //封裝一個(gè)好畫圓的方法
        drawArc: function (obj, x, y, radius, startAngle, endAngle, globalAlpha, fillStyle) {
            obj.ctx.beginPath();
            obj.ctx.arc(x, y, radius, startAngle, endAngle);
            obj.ctx.closePath();
            obj.ctx.globalAlpha = globalAlpha;
            obj.ctx.fillStyle = fillStyle;
            obj.ctx.fill();
        },
        display: function (obj) {
            if (obj.index == 1) {
                obj.ani1(obj);
            } else {
                console.warn('請(qǐng)注意,沒有第' + obj.index + '個(gè)效果??!');
            }
        },
        ani1: function (obj) {
        },
    };
    var defaults = {
        color: false
    };
    $.fn.btnHoverAni = function (index, config) {
        defaults = {
            color: false
        };
        $.extend(defaults, config);
        $(this).each(function () {
            new ButtonHoverAnimation($(this), index, defaults);
        });
    }
})(jQuery);

我們通過傳入index來確定動(dòng)畫效果,在插件內(nèi)通過display方法來尋找對(duì)應(yīng)的效果并執(zhí)行,這里我們預(yù)留了一個(gè)ani1來實(shí)現(xiàn)我們的動(dòng)畫。
我們可以看到第一個(gè)效果很簡(jiǎn)單,就是鼠標(biāo)移入,圓圈放大,鼠標(biāo)移出,圓圈縮小,圓圈的最大半徑是中心到頂點(diǎn)的距離。(填充的隨機(jī)色在上面已經(jīng)寫好了,使用obj.color即可)。
ani1是我們執(zhí)行每一幀的方法,那么一些不需要一直計(jì)算的值(比如最大半徑)在display中計(jì)算好即可:

 if (obj.index == 1) {
                obj.radius = 0;
                obj.radiusMax = Math.sqrt(Math.pow(obj.width / 2, 2) + Math.pow(obj.height / 2, 2));
                if (!obj.run) {
                    obj.run = true;
                    obj.ani1(obj);
                }
            }

然后實(shí)現(xiàn)動(dòng)畫效果

ani1: function (obj) {
            obj.ctx.clearRect(0, 0, obj.width, obj.height);
            if (obj.mouseenter) {
                obj.radius += 5;
            } else {
                obj.radius -= 5;
            }

            if (obj.radius >= obj.radiusMax) {
                obj.radius = obj.radiusMax;
            } else if (obj.radius <= 0) {
                obj.radius = 0;
            }
            obj.drawArc(obj, obj.width / 2, obj.height / 2, obj.radius, 0, Math.PI * 2, 1, obj.color);
            if (obj.mouseenter || obj.radius > 0) {
                requestAnimationFrame(function () {
                    obj.ani1(obj);
                })
            } else {
                obj.radius = 0;
                obj.ctx.clearRect(0, 0, obj.width, obj.height);
                obj.run = false;
            }
        }

這樣第一個(gè)簡(jiǎn)單的動(dòng)畫就做完了,后面添加動(dòng)畫,只需要在display方法中添加分支即可。

第二個(gè)效果

第二個(gè)效果

第二個(gè)效果就是一個(gè)轉(zhuǎn)圈了,這里我們以長(zhǎng)方形中心為圓心,圓心到頂點(diǎn)的距離為半徑,每幀都自正上方順時(shí)針畫圓弧即可(超過canvas的內(nèi)容是看不到的)。鼠標(biāo)移入時(shí),每一幀掃過弧度自增,鼠標(biāo)移出時(shí)自減。
首先是添加一個(gè)動(dòng)畫分支:

else if (obj.index == 2) {
                if (obj.array.length == 0) {
                    obj.radius = Math.sqrt(Math.pow(obj.width / 2, 2) + Math.pow(obj.height / 2, 2));
                    obj.array = [obj.width / 2, obj.height / 2, obj.radius, -90];
                }
                if (!obj.run) {
                    obj.run = true;
                    obj.ani2(obj);
                }
            }

同樣是不需要重復(fù)計(jì)算值優(yōu)先計(jì)算下來,我們記下了一個(gè)數(shù)組來確定圓弧的圓心和掃過的結(jié)束角度。然后是具體實(shí)現(xiàn):

  ani2: function (obj) {
            obj.ctx.clearRect(0, 0, obj.width, obj.height);
            if (obj.mouseenter) {
                obj.array[3] += 15;
            } else {
                obj.array[3] -= 15;
            }
            if (obj.array[3] >= 270) {
                //掃完一圈
                obj.array[3] = 270;
            } else if (obj.array[3] <= -90) {
                //回到原位
                obj.array[3] = -90;
            }

            obj.ctx.globalAlpha = 1;
            obj.ctx.fillStyle = obj.color;
            obj.ctx.beginPath();
            obj.ctx.moveTo(obj.array[0], obj.array[1]);
             //開始角度為-90,即圓心正上方開始
            obj.ctx.arc(obj.array[0], obj.array[1], obj.array[2], -90 * Math.PI / 180, obj.array[3] * Math.PI / 180);
            obj.ctx.closePath();
            obj.ctx.fill();
            if (obj.mouseenter || obj.array[3] > -90) {
                requestAnimationFrame(function () {
                    obj.ani2(obj);
                });
            } else {
                obj.array = [];
                obj.ctx.clearRect(0, 0, obj.width, obj.height);
                obj.run = false;
            }
        }

第三個(gè)效果

第三個(gè)效果

gif壓縮后好像有點(diǎn)掉幀,實(shí)際上的效果就是首先上下各填充至三分之一處,然后中間的三分之一左右填充。
為了描述形象一點(diǎn)我決定秀一波畫作:

圖片.png

那么事實(shí)上就是一開始在長(zhǎng)方塊四周畫四個(gè)長(zhǎng)方塊,上下的高度各為總高的三分之一,左右的寬度各為總寬的二分之一,我們能看到的是寬w高h(yuǎn)的區(qū)域。鼠標(biāo)移入時(shí),首先移動(dòng)上下方塊,當(dāng)各自移動(dòng)三分之一時(shí),開始移動(dòng)左右方塊,直至填滿。

else if (obj.index == 3) {
                if (obj.array.length == 0) {
                    obj.rectHeight = Math.ceil(obj.height / 3);
                    obj.array[0] = {x: 0, y: -obj.rectHeight, w: obj.width, h: obj.rectHeight};
                    obj.array[1] = {x: 0, y: obj.height, w: obj.width, h: obj.rectHeight};
                    obj.array[2] = {x: -obj.width / 2, y: obj.rectHeight, w: obj.width / 2, h: obj.rectHeight};
                    obj.array[3] = {x: obj.width, y: obj.rectHeight, w: obj.width / 2, h: obj.rectHeight};
                    obj.ySpeed = obj.rectHeight / 10;
                    obj.xSpeed = obj.width / 20;
                }
                if (!obj.run) {
                    obj.run = true;
                    obj.ani3(obj);
                }
            } 

我們記下方塊的高度三分之一,四個(gè)方塊的坐標(biāo),以及上下移動(dòng)速度ySpeed和左右移動(dòng)速度xSpeed,接著具體實(shí)現(xiàn)如下:

ani3: function (obj) {
            obj.ctx.clearRect(0, 0, obj.width, obj.height);
            if (obj.mouseenter) {
                obj.array[0].y += obj.ySpeed;
                obj.array[1].y -= obj.ySpeed;
                if (obj.array[0].y >= 0) {
                    obj.array[0].y = 0;
                    obj.array[1].y = obj.height - obj.rectHeight;
                    obj.array[2].x += obj.xSpeed;
                    obj.array[3].x -= obj.xSpeed;
                    if (obj.array[2].x >= 0) {
                        obj.array[2].x = 0;
                        obj.array[3].x = obj.width / 2;
                    }
                }
            } else {
                obj.array[2].x -= obj.xSpeed;
                obj.array[3].x += obj.xSpeed;
                if (obj.array[2].x <= -obj.width / 2) {
                    obj.array[2].x = -obj.width / 2;
                    obj.array[3].x = obj.width;
                    obj.array[0].y -= obj.ySpeed;
                    obj.array[1].y += obj.ySpeed;
                    if (obj.array[0].y <= -obj.rectHeight) {
                        obj.array[0].y = -obj.rectHeight;
                        obj.array[1].y = obj.height;
                    }
                }
            }
            obj.ctx.globalAlpha = 1;
            obj.ctx.fillStyle = obj.color;
            for (var i = 0; i < obj.array.length; i++) {
                obj.ctx.fillRect(obj.array[i].x, obj.array[i].y, obj.array[i].w, obj.array[i].h);
            }

            if (obj.mouseenter || obj.array[0].y > -obj.rectHeight) {
                requestAnimationFrame(function () {
                    obj.ani3(obj);
                });
            } else {
                obj.ctx.clearRect(0, 0, obj.width, obj.height);
                obj.array = [];
                obj.run = false;
            }
        }

鼠標(biāo)移入時(shí),當(dāng)?shù)谝粋€(gè)方塊的左上角坐標(biāo)y大于等于0時(shí),左右方塊開始移動(dòng)直至填滿,鼠標(biāo)移出反之即可。

第四個(gè)效果

基于第三個(gè)效果,我就又想了一個(gè)效果,就是四邊四個(gè)三角形同時(shí)移入移出,先看展示效果:

第四個(gè)效果

原理是一樣的,所以直接上代碼了:

else if (obj.index == 4) {
                if (obj.array.length == 0) {
                    obj.array[0] = {x: obj.width / 2, y: 0};
                    obj.array[1] = {x: obj.width / 2, y: obj.height};
                    obj.array[2] = {x: 0, y: obj.height / 2};
                    obj.array[3] = {x: obj.width, y: obj.height / 2};
                    obj.ySpeed = obj.height / 40;
                    obj.xSpeed = obj.width / 40;
                }
                if (!obj.run) {
                    obj.run = true;
                    obj.ani4(obj);
                }

            }

我這里只記下四個(gè)三角形頂點(diǎn)的左邊,因?yàn)槠渌麅牲c(diǎn)的坐標(biāo)可以通過頂點(diǎn)坐標(biāo)來計(jì)算。為了同時(shí)移入,ySpeed和xSpeed根據(jù)寬高等分各自定義。

 ani4: function (obj) {
            obj.ctx.clearRect(0, 0, obj.width, obj.height);
            if (obj.mouseenter) {
                obj.array[0].y += obj.ySpeed;
                obj.array[1].y -= obj.ySpeed;
                obj.array[2].x += obj.xSpeed;
                obj.array[3].x -= obj.xSpeed;
                if (obj.array[0].y >= obj.height / 2) {
                    obj.array[0].y = obj.array[1].y = obj.height / 2;
                    obj.array[2].x = obj.array[3].x = obj.width / 2;
                }
            } else {
                obj.array[0].y -= obj.ySpeed;
                obj.array[1].y += obj.ySpeed;
                obj.array[2].x -= obj.xSpeed;
                obj.array[3].x += obj.xSpeed;
                if (obj.array[0].y <= 0) {
                    obj.array[0].y = 0;
                    obj.array[1].y = obj.height;
                    obj.array[2].x = 0;
                    obj.array[3].x = obj.width;
                }
            }

            for (var i = 0; i < obj.array.length; i++) {
                obj.ctx.globalAlpha = 1;
                obj.ctx.fillStyle = obj.color;
                if (obj.array[0].y >= obj.height / 2) {
                    obj.ctx.fillRect(0, 0, obj.width, obj.height);
                } else {
                    obj.ctx.beginPath();
                    obj.ctx.moveTo(obj.array[i].x, obj.array[i].y);
                    obj.ctx.lineTo(i == 3 ? obj.array[i].x + obj.width / 2 : obj.array[i].x - obj.width / 2,
                        i == 1 ? obj.array[i].y + obj.height / 2 : obj.array[i].y - obj.height / 2);
                    obj.ctx.lineTo(i == 2 ? obj.array[i].x - obj.width / 2 : obj.array[i].x + obj.width / 2,
                        i == 0 ? obj.array[i].y - obj.height / 2 : obj.array[i].y + obj.height / 2);
                    obj.ctx.fill();
                }
            }
            if (obj.mouseenter || obj.array[0].x > 0) {
                requestAnimationFrame(function () {
                    obj.ani4(obj);
                });
            } else {
                obj.ctx.clearRect(0, 0, obj.width, obj.height);
                obj.array = [];
                obj.run = false;
            }
        }

lineTo的計(jì)算其實(shí)四個(gè)三角形分別寫清晰一點(diǎn),我這里為了縮短代碼長(zhǎng)度所以這么寫了,看著有點(diǎn)亂= 。= 。

第五個(gè)效果

第五個(gè)效果

這個(gè)效果就是隨機(jī)畫圓,我們用一個(gè)數(shù)組去存儲(chǔ)這些圓,每次繪制數(shù)組中所有的圓,然后每個(gè)圓的透明度不斷變小,半徑不斷變大,當(dāng)透明度減到0以下時(shí),從數(shù)組中移除這個(gè)圓。圓心的位置通過隨機(jī)數(shù)來確定,即Math.random() 乘以obj.width和Math.random() 乘以obj.height
下面看代碼:

else if (obj.index == 5) {
                if (obj.array.length == 0) {
                    obj.ani5(obj);
                }
            }

 ani5: function (obj) {
            obj.ctx.clearRect(0, 0, obj.width, obj.height);
            //第二個(gè)條件是為了控制產(chǎn)生速率
            if (obj.mouseenter && Math.random() < .5) {
                obj.array.push({
                    x: Math.random() * obj.width,
                    y: Math.random() * obj.height,
                    radius: 1,
                    alpha: 1,
                    color: obj.color
                })
            }

            for (var i = 0; i < obj.array.length; i++) {
                obj.drawArc(obj, obj.array[i].x, obj.array[i].y, obj.array[i].radius, 0, 2 * Math.PI, obj.array[i].alpha, obj.array[i].color);
                obj.array[i].alpha -= .02;
                obj.array[i].radius += .4;
                if (obj.array[i].alpha <= 0) {
                    obj.array.splice(i, 1);
                    i--;
                }
            }
            obj.ctx.globalAlpha = 1;
            if (obj.mouseenter || obj.array.length > 0) {
                requestAnimationFrame(function () {
                    obj.ani5(obj);
                })
            } else {
                obj.ctx.clearRect(0, 0, obj.width, obj.height);
            }

        }

第六個(gè)效果

第六個(gè)效果

這個(gè)效果即在鼠標(biāo)的位置產(chǎn)生隨機(jī)向四周移動(dòng)的圓圈,主要的技術(shù)點(diǎn)就在于圓圈隨機(jī)移動(dòng)的方向了,所以只能由我靈魂畫師來解釋了:

圖片.png

假設(shè)圓圈在p1點(diǎn),下一幀移動(dòng)到p2點(diǎn),移動(dòng)距離為1,假設(shè)角度為a,那么p2點(diǎn)的坐標(biāo)即為x1+cosa,y1+sina。這樣下來,只要角度取一個(gè)隨機(jī)值,即可實(shí)現(xiàn)向四面八方擴(kuò)散的效果。需要注意是的移除圓圈的條件,因?yàn)閳A圈完全移動(dòng)出方塊,是需要考慮圓圈的半徑的。
代碼如下:

else if (obj.index == 6) {
                if (obj.array.length == 0) {
                    obj.ani6(obj);
                }
            }

  ani6: function (obj) {
            obj.ctx.clearRect(0, 0, obj.width, obj.height);
            if (obj.mouseenter) {
                obj.radius = Math.random() * 3 + 1;
                obj.direction = Math.random() * Math.PI * 2;
                obj.array.push({x: obj.x, y: obj.y, radius: obj.radius, direction: obj.direction})
            }

            for (var i = 0; i < obj.array.length; i++) {
                obj.drawArc(obj, obj.array[i].x, obj.array[i].y, obj.array[i].radius, 0, 2 * Math.PI, 0.6, obj.color);
                obj.array[i].x += Math.cos(obj.array[i].direction);
                obj.array[i].y += Math.sin(obj.array[i].direction);
                //注意移除圓圈的條件需要考慮半徑
                if (obj.array[i].y > obj.array[i].radius + obj.height || obj.array[i].y < -obj.array[i].radius || obj.array[i].x < -obj.array[i].radius || obj.array[i].x > obj.array[i].radius + obj.width) {
                    obj.array.splice(i, 1);
                    i--;
                }
            }

            obj.ctx.globalAlpha = 1;
            if (obj.mouseenter || obj.array.length > 0) {
                requestAnimationFrame(function () {
                    obj.ani6(obj);
                })
            } else {
                obj.ctx.clearRect(0, 0, obj.width, obj.height);
            }
        }

最后我們?cè)賮砜催@樣一個(gè)效果:

第七個(gè)效果

可以看到,這個(gè)效果實(shí)際上也就是上兩個(gè)效果加在一起而已,就如何實(shí)現(xiàn)抖動(dòng)而言,上個(gè)效果方向是固定的,我們這里移動(dòng)的xy都取隨機(jī),如下:

    obj.array[i].x += Math.cos(Math.random() * 2 * Math.PI);
    obj.array[i].y += Math.sin(Math.random() * 2 * Math.PI);

或者兩個(gè)取相同的隨機(jī)角度也行,都可以實(shí)現(xiàn)抖動(dòng)的效果。

結(jié)語

效果還有很多就不多說了,有興趣的同學(xué)可以再去研究研究。但是只要明白原理,再復(fù)雜的動(dòng)畫拆解了我們都可以明了。
博主對(duì)于前端的動(dòng)畫比較感興趣,一些比較酷炫的動(dòng)畫以后也會(huì)持續(xù)更新,這里先激勵(lì)自己一波。

附上演示地址:
http://jsrun.net/pPYKp
github地址:
https://github.com/bestneville/JqueryBtnHoverAni

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Core Animation Core Animation,中文翻譯為核心動(dòng)畫,它是一組非常強(qiáng)大的動(dòng)畫處理API,...
    45b645c5912e閱讀 3,151評(píng)論 0 21
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,687評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫全貌。在這里你可以看...
    F麥子閱讀 5,260評(píng)論 5 13
  • 第三天,結(jié)束。
    米修a米修閱讀 210評(píng)論 0 0
  • 創(chuàng)融商學(xué)院,系出名門,前身為清華大學(xué)深圳研究生院培訓(xùn)學(xué)院直屬部門——金融研修中心,依托于清華大學(xué)深圳研究生院校友會(huì)...
    創(chuàng)融商學(xué)院閱讀 506評(píng)論 0 1

友情鏈接更多精彩內(nèi)容