美文网首页
Jquery插件之hover动画

Jquery插件之hover动画

作者: 菜小轩526 | 来源:发表于2017-07-12 19:31 被阅读0次

    前言

    前一阵子网上看到一个写的挺好的动画插件,于是下载了下来。这两天阅读了一下代码,然后决定分享一下原作者的思路。原效果链接找了半天找不到了,如果有看客留意到了可以留言,我附上链接(混淆后叫xs.js,可以在后面的github链接上下载)。

    第一个效果

    我在这里为了加深印象重敲了一遍,摘取部分有代表性的效果来讲,首先我们看第一个效果图:

    第一个效果

    我们所期望的是通过插件注册一下容器即可产生效果,且插件拥有很多动画种类,首先是页面:

    <!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,注释我添加的应该算详细了

    (function () {
        function ButtonHoverAnimation(btn, index, defaults) {
            //调用者
            this.btn = btn;
            //调用索引,哪一个效果
            this.index = index;
            this.color = defaults.color;
            this.init();
        }
    
        ButtonHoverAnimation.prototype = {
            init: function () {
                //后面的方法都使用这个obj对象的参数值
                var obj = this;
                obj.run = false;
                if (obj.btn.css('position') != 'fixed' && obj.btn.css('position') != 'absolute') {
                    obj.btn.css('position', 'relative');
                }
                //宽高即调用者的宽高
                obj.width = obj.btn.width();
                obj.height = obj.btn.height();
                //将调用者的子内容层级提高,并且在最底层添加一个与调用者等宽高的画布canvas
                //这样,我们的在canvas上的绘制作为背景不会影响到内容的展示
                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'});
                    }
                });
                //背景色默认白色
                if (obj.btn.css('background-color') != "rgba(0, 0, 0, 0)") {
                    obj.backgroundColor = obj.btn.css('background-color');
                } else {
                    obj.backgroundColor = '#ffffff';
                }
                //添加画布,层级最低
                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');
                //画笔的颜色,如果调用者没有传入,每次鼠标移入初始化一个随机色
                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;
                }
                //鼠标移动时记录坐标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 = [];
                //鼠标移入移出时,我们记录一个mouseenter变量,并且移入时使用display方法去寻找对应的效果
                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);
            },
                //封装一个好画圆的方法
            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('请注意,没有第' + obj.index + '个效果!!');
                }
            },
            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来确定动画效果,在插件内通过display方法来寻找对应的效果并执行,这里我们预留了一个ani1来实现我们的动画。
    我们可以看到第一个效果很简单,就是鼠标移入,圆圈放大,鼠标移出,圆圈缩小,圆圈的最大半径是中心到顶点的距离。(填充的随机色在上面已经写好了,使用obj.color即可)。
    ani1是我们执行每一帧的方法,那么一些不需要一直计算的值(比如最大半径)在display中计算好即可:

     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);
                    }
                }
    

    然后实现动画效果

    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;
                }
            }
    

    这样第一个简单的动画就做完了,后面添加动画,只需要在display方法中添加分支即可。

    第二个效果

    第二个效果

    第二个效果就是一个转圈了,这里我们以长方形中心为圆心,圆心到顶点的距离为半径,每帧都自正上方顺时针画圆弧即可(超过canvas的内容是看不到的)。鼠标移入时,每一帧扫过弧度自增,鼠标移出时自减。
    首先是添加一个动画分支:

    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);
                    }
                }
    

    同样是不需要重复计算值优先计算下来,我们记下了一个数组来确定圆弧的圆心和扫过的结束角度。然后是具体实现:

      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;
                }
            }
    

    第三个效果

    第三个效果

    gif压缩后好像有点掉帧,实际上的效果就是首先上下各填充至三分之一处,然后中间的三分之一左右填充。
    为了描述形象一点我决定秀一波画作:

    图片.png

    那么事实上就是一开始在长方块四周画四个长方块,上下的高度各为总高的三分之一,左右的宽度各为总宽的二分之一,我们能看到的是宽w高h的区域。鼠标移入时,首先移动上下方块,当各自移动三分之一时,开始移动左右方块,直至填满。

    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);
                    }
                } 
    

    我们记下方块的高度三分之一,四个方块的坐标,以及上下移动速度ySpeed和左右移动速度xSpeed,接着具体实现如下:

    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;
                }
            }
    

    鼠标移入时,当第一个方块的左上角坐标y大于等于0时,左右方块开始移动直至填满,鼠标移出反之即可。

    第四个效果

    基于第三个效果,我就又想了一个效果,就是四边四个三角形同时移入移出,先看展示效果:

    第四个效果

    原理是一样的,所以直接上代码了:

    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);
                    }
    
                }
    

    我这里只记下四个三角形顶点的左边,因为其他两点的坐标可以通过顶点坐标来计算。为了同时移入,ySpeed和xSpeed根据宽高等分各自定义。

     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的计算其实四个三角形分别写清晰一点,我这里为了缩短代码长度所以这么写了,看着有点乱= 。= 。

    第五个效果

    第五个效果

    这个效果就是随机画圆,我们用一个数组去存储这些圆,每次绘制数组中所有的圆,然后每个圆的透明度不断变小,半径不断变大,当透明度减到0以下时,从数组中移除这个圆。圆心的位置通过随机数来确定,即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);
                //第二个条件是为了控制产生速率
                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);
                }
    
            }
    

    第六个效果

    第六个效果

    这个效果即在鼠标的位置产生随机向四周移动的圆圈,主要的技术点就在于圆圈随机移动的方向了,所以只能由我灵魂画师来解释了:

    图片.png

    假设圆圈在p1点,下一帧移动到p2点,移动距离为1,假设角度为a,那么p2点的坐标即为x1+cosa,y1+sina。这样下来,只要角度取一个随机值,即可实现向四面八方扩散的效果。需要注意是的移除圆圈的条件,因为圆圈完全移动出方块,是需要考虑圆圈的半径的。
    代码如下:

    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);
                }
            }
    

    最后我们再来看这样一个效果:

    第七个效果

    可以看到,这个效果实际上也就是上两个效果加在一起而已,就如何实现抖动而言,上个效果方向是固定的,我们这里移动的xy都取随机,如下:

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

    或者两个取相同的随机角度也行,都可以实现抖动的效果。

    结语

    效果还有很多就不多说了,有兴趣的同学可以再去研究研究。但是只要明白原理,再复杂的动画拆解了我们都可以明了。
    博主对于前端的动画比较感兴趣,一些比较酷炫的动画以后也会持续更新,这里先激励自己一波。

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

    相关文章

      网友评论

          本文标题:Jquery插件之hover动画

          本文链接:https://www.haomeiwen.com/subject/cjymhxtx.html