canvas基础学习(四)

今天逛天猫时,看见优衣库店铺首页有个这个飘雪效果,顿时觉得好酷炫,立马从里面copy代码进行学习。

之前我也做过一些canvas特效,往往在canvas全屏时,canvas下层的div就无法进行dom的事件操作,点击之类的就失灵了。之前我的做法要么就是在canvas上加入点击事件,穿透到下层,或者把下层的div通过z-index属性放在canvas的上层。这种办法都显得死板或者展现效果很差。看了这个页面发现了css3的解决办法

.snow-canvas {
    display: block;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    position: fixed;
    pointer-events: none;
}

就是通过pointer-events设置为none,可以让事件自动到下层去,不过坏处也有,就是通过F12开发者工具不容易找到canvas这个元素。不禁再次感慨CSS3的强大。

下面是把页面的JS copy出来的做的demo

html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            *{
                padding: 0;
                margin: 0;
            }
            .main {
                display: block;
                width: 100%;
                height: 2000px;
                top: 0;
                left: 0;
                background-color: red;
            }
            .snow-canvas {
                display: block;
                width: 100%;
                height: 100%;
                top: 0;
                left: 0;
                position: fixed;
                pointer-events: none;
            }
        </style>
    </head>
    <body>
        <div class="main"></div>
        <canvas class="snow-canvas" speed="1" interaction="false" size="2" count="80" opacity="0.00001" start-color="rgba(253,252,251,1)" end-color="rgba(251,252,253,0.3)" wind-power="0" image="false" width="1272" height="150"></canvas>
        <canvas class="snow-canvas" speed="3" interaction="true" size="6" count="30" start-color="rgba(253,252,251,1)" end-color="rgba(251,252,253,0.3)" opacity="0.00001" wind-power="2" image="false" width="1272" height="150"></canvas>
        <canvas class="snow-canvas" speed="3" interaction="true" size="12" count="20" wind-power="-5" image="img/snow.png" width="1272" height="150"></canvas>
    </body>
    <script type="text/javascript" src="js/zepto.min.js" ></script>
    <script type="text/javascript" src="js/main1.js" ></script>
    <script>
        $(function() {
            $(".snow-canvas").snow();
            $(".main").click(function() {
                alert(111)
            });
        });
    </script>

</html>

main1.js

(function($) {
    var $window = window,
        $timeout = setTimeout;
    var supportCanvas = function() {
        var eCan = document.createElement("canvas");
        return (typeof eCan.getContext) == "function";
    };
    window.Snow = function(element, settings) {
        (function() {
            var lastTime = 0;
            var vendors = ['webkit', 'moz'];
            for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
                window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
                window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || // name has changed in Webkit
                    window[vendors[x] + 'CancelRequestAnimationFrame'];
            }
            if (!window.requestAnimationFrame) {
                window.requestAnimationFrame = function(callback, element) {
                    var timeToCall = 14; //freezes in safari for windows ,and mac to , so i change time to call with 14;
                    var id = window.setTimeout(function() {
                        callback(timeToCall);
                    }, timeToCall);
                    return id;
                };
            }
            if (!window.cancelAnimationFrame) {
                window.cancelAnimationFrame = function(id) {
                    clearTimeout(id);
                };
            }
        }());
        this.settings = settings,
            this.flakes = [],
            this.flakeCount = settings.count,
            this.mx = -100,
            this.my = -100,
            this.init(element)
    };
    Snow.prototype.init = function(element) {
        this.canvas = element.get(0), this.ctx = this.canvas.getContext("2d"), this.canvas.width = $window.innerWidth, this.canvas.height = $window.innerHeight, this.flakes = [];
        for (var i = 0; i < this.flakeCount; i++) {
            var x = Math.floor(Math.random() * this.canvas.width),
                y = Math.floor(Math.random() * this.canvas.height),
                size = Math.floor(100 * Math.random()) % this.settings.size + 2,
                speed = Math.floor(100 * Math.random()) % this.settings.speed + Math.random() * size / 10 + .5,
                opacity = .5 * Math.random() + this.settings.opacity;
            this.flakes.push({
                speed: speed,
                velY: speed,
                velX: 0,
                x: x,
                y: y,
                size: size,
                stepSize: Math.random() / 30,
                step: 0,
                angle: 180,
                opacity: opacity
            })
        }
        1 == this.settings.interaction && this.canvas.addEventListener("mousemove", function(e) {
            this.mx = e.clientX, this.my = e.client
        });
        var thiz = this;
        $($window).resize(function() {
            thiz.ctx.clearRect(0, 0, thiz.canvas.width, thiz.canvas.height), thiz.canvas.width = $window.innerWidth, thiz.canvas.height = $window.innerHeight
        });
        if (typeof this.settings.image === "string") {
            this.image = $("<img src='" + this.settings.image + "' style='display: none'>");
        };
        this.snow();
    }, Snow.prototype.snow = function() {
        var thiz = this,
            render = function() {
                thiz.ctx.clearRect(0, 0, thiz.canvas.width, thiz.canvas.height);
                for (var i = 0; i < thiz.flakeCount; i++) {
                    var flake = thiz.flakes[i],
                        x = thiz.mx,
                        y = thiz.my,
                        minDist = 100,
                        x2 = flake.x,
                        y2 = flake.y,
                        dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y));
                    if (minDist > dist) {
                        var force = minDist / (dist * dist),
                            xcomp = (x - x2) / dist,
                            ycomp = (y - y2) / dist,
                            deltaV = force / 2;
                        flake.velX -= deltaV * xcomp, flake.velY -= deltaV * ycomp
                    } else
                        switch (flake.velX *= .98, flake.velY <= flake.speed && (flake.velY = flake.speed), thiz.settings.windPower) {
                            case !1:
                                flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
                                break;
                            case 0:
                                flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
                                break;
                            default:
                                flake.velX += .01 + thiz.settings.windPower / 100
                        }
                    if (flake.y += flake.velY, flake.x += flake.velX, (flake.y >= thiz.canvas.height || flake.y <= 0) && thiz.resetFlake(flake), (flake.x >= thiz.canvas.width || flake.x <= 0) && thiz.resetFlake(flake), 0 == thiz.settings.image) {
                        var grd = thiz.ctx.createRadialGradient(flake.x, flake.y, 0, flake.x, flake.y, flake.size - 1);
                        grd.addColorStop(0, thiz.settings.startColor), grd.addColorStop(1, thiz.settings.endColor), thiz.ctx.fillStyle = grd, thiz.ctx.beginPath(), thiz.ctx.arc(flake.x, flake.y, flake.size, 0, 2 * Math.PI), thiz.ctx.fill()
                    } else
                        thiz.ctx.drawImage(thiz.image.get(0), flake.x, flake.y, 2 * flake.size, 2 * flake.size)
                }
                $window.cancelAnimationFrame(render), $window.requestAnimationFrame(render)
            };
        render()
    }, Snow.prototype.resetFlake = function(flake) {
        if (0 == this.settings.windPower || 0 == this.settings.windPower)
            flake.x = Math.floor(Math.random() * this.canvas.width), flake.y = 0;
        else if (this.settings.windPower > 0) {
            var xarray = Array(Math.floor(Math.random() * this.canvas.width), 0),
                yarray = Array(0, Math.floor(Math.random() * this.canvas.height)),
                allarray = Array(xarray, yarray),
                selected_array = allarray[Math.floor(Math.random() * allarray.length)];
            flake.x = selected_array[0], flake.y = selected_array[1]
        } else {
            var xarray = Array(Math.floor(Math.random() * this.canvas.width), 0),
                yarray = Array(this.canvas.width, Math.floor(Math.random() * this.canvas.height)),
                allarray = Array(xarray, yarray),
                selected_array = allarray[Math.floor(Math.random() * allarray.length)];
            flake.x = selected_array[0], flake.y = selected_array[1]
        }
        flake.size = Math.floor(100 * Math.random()) % this.settings.size + 2,
            flake.speed = Math.floor(100 * Math.random()) % this.settings.speed + Math.random() * flake.size / 10 + .5,
            flake.velY = flake.speed, flake.velX = 0, flake.opacity = .5 * Math.random() + this.settings.opacity
    };
    $.fn.snow = function() {
        var userCanvas = supportCanvas();
        userCanvas && $(this).each(function(i, e) {
            var scope = {};
            $.each(e.attributes, function(index, key) {
                scope[$.camelCase(key.name)] = Number(Number(key.value)) ? Number(key.value) : key.value
            });
            if (typeof scope.image === "string" && scope.image === "false") {
                scope.image = false
            };
            new Snow($(e), {
                speed: 1 || 0,
                interaction: scope.interaction || !0,
                size: scope.size || 2,
                count: scope.count || 200,
                opacity: scope.opacity || 1,
                startColor: scope.startColor || "rgba(255,255,255,1)",
                endColor: scope.endColor || "rgba(255,255,255,0)",
                windPower: scope.windPower || 0,
                image: scope.image || !1
            });
        });
        if (!userCanvas) {
            var setting = {};
            $(this).each(function(i, e) {
                setting["image"] = $(e).attr("image") || "./imgs/snow.png";
                $(this).remove();
                createSnow("", 40);
            });
        };
    };

    function k(a, b, c) {
        if (a.addEventListener) a.addEventListener(b, c, false);
        else a.attachEvent && a.attachEvent("on" + b, c)
    }

    function g(a) {
        if (typeof window.onload != "function") window.onload = a;
        else {
            var b = window.onload;
            window.onload = function() {
                b();
                a()
            }
        }
    }

    function h() {
        var a = {};
        for (type in {
                Top: "",
                Left: ""
            }) {
            var b = type == "Top" ? "Y" : "X";
            if (typeof window["page" + b + "Offset"] != "undefined")
                a[type.toLowerCase()] = window["page" + b + "Offset"];
            else {
                b = document.documentElement.clientHeight ? document.documentElement : document.body;
                a[type.toLowerCase()] = b["scroll" + type]
            }
        }
        return a
    }

    function l() {
        var a = document.body,
            b;
        if (window.innerHeight) b = window.innerHeight;
        else if (a.parentElement.clientHeight) b = a.parentElement.clientHeight;
        else if (a && a.clientHeight) b = a.clientHeight;
        return b
    };
    var j = true;
    var f = true;
    var m = null;
    var c = [];
    var createSnow = function(a, b) {
        clearInterval(m);
        c = [];
        m = setInterval(function() {
            f && b > c.length && Math.random() < b * 0.0025 && c.push(new i(a));
            !f && !c.length && clearInterval(m);
            for (var e = h().top, n = l(), d = c.length - 1; d >= 0; d--)
                if (c[d])
                    if (c[d].top < e || c[d].top + c[d].size + 1 > e + n) {
                        c[d].remove();
                        c[d] = null;
                        c.splice(d, 1)
                    } else {
                        c[d].move();
                        c[d].draw()
                    }
        }, 40);
        k(window, "scroll",
            function() {
                for (var e = c.length - 1; e >= 0; e--) c[e].draw()
            })
    };
    var removeSnow = function() {
        clearInterval(m);
        do {
            c.pop().remove();
        } while (c.length);
    };
    //雪花的构造函数;
    function i(a) {
        this.parent = document.body;
        this.createEl(this.parent, a);
        this.size = Math.random() * 20 + 20;
        this.el.style.width = Math.round(this.size) + "px";
        this.el.style.height = Math.round(this.size) + "px";
        this.maxLeft = document.body.offsetWidth - this.size;
        this.maxTop = document.body.offsetHeight - this.size;
        this.left = Math.random() * this.maxLeft;
        this.top = h().top + 1;
        this.angle = 1.4 + 0.8 * Math.random();
        this.minAngle = 1.4;
        this.maxAngle = 1.6;
        this.angleDelta = 0.01 * Math.random();
        this.speed = 2 + Math.random()
    }
    i.prototype = {
        createEl: function(a, b) {
            this.el = document.createElement("img");
            this.el.classname = "nicesnowclass";
            this.el.setAttribute("src", b || "./imgs/snow.png");
            this.el.style.position = "absolute";
            this.el.style.display = "block";
            this.el.style.zIndex = "99999";
            this.parent.appendChild(this.el)
        },
        move: function() {
            if (this.angle < this.minAngle || this.angle > this.maxAngle)
                this.angleDelta = -this.angleDelta;
            this.angle += this.angleDelta;
            this.left += this.speed * Math.cos(this.angle * Math.PI);
            this.top -= this.speed * Math.sin(this.angle * Math.PI);
            if (this.left < 0) this.left = this.maxLeft;
            else if (this.left > this.maxLeft) this.left = 0
        },
        draw: function() {
            this.el.style.top = Math.round(this.top) + "px";
            this.el.style.left = Math.round(this.left) + "px"
        },
        remove: function() {
            this.parent.removeChild(this.el);
            this.parent = this.el = null
        }
    };
})(Zepto);

效果图为

雪花的图片为

这个代码主要是用于PC,代码上做了很多的兼容操作,无论是动画时间戳、页面对canvas的支持、css等等。我个人是做移动端的开发,这段代码有很多浏览器兼容处理对我并不是很需要,并且移动端更加专注于代码代码执行速度和内存的使用,这段代码算法比较复杂,而且它有个问题是当页面有多个canvas时,他建立多个Snow对象,每一个Snow对象都会开启一个时间戳函数,如下图,这个在移动端是比较影响性能的,我们更加希望一个时间戳里完成多个Snow对象的操作。

 

所以我把main1.js剔除了一些代码,然后略微改动下,如下代码

(function($ , window) {
        var snows = [];
        var supportCanvas = function() {
            return (typeof document.createElement("canvas").getContext) == "function";
        };
        function Snow(element, settings) {
            this.settings = settings,this.flakes = [],this.flakeCount = settings.count,
                this.mx = -100,this.my = -100,this.init(element);
        };
        Snow.prototype.init = function(element) {
            this.canvas = element[0], this.ctx = this.canvas.getContext("2d"), this.canvas.width = window.innerWidth, this.canvas.height = window.innerHeight, this.flakes = [];
            for (var i = 0; i < this.flakeCount; i++) {
                var x = Math.floor(Math.random() * this.canvas.width),
                    y = Math.floor(Math.random() * this.canvas.height),
                    size = Math.floor(100 * Math.random()) % this.settings.size + 2,
                    speed = Math.floor(100 * Math.random()) % this.settings.speed + Math.random() * size / 10 + .5,
                    opacity = .5 * Math.random() + this.settings.opacity;
                this.flakes.push({
                    speed: speed,
                    velY: speed,
                    velX: 0,
                    x: x,
                    y: y,
                    size: size,
                    stepSize: Math.random() / 1000,
                    step: 0,
                    angle: 180,
                    opacity: opacity
                })
            }
            var thiz = this;
            $(window).resize(function() {
                thiz.ctx.clearRect(0, 0, thiz.canvas.width, thiz.canvas.height), thiz.canvas.width = window.innerWidth, thiz.canvas.height = window.innerHeight
            });
            if (typeof this.settings.image === "string") {
                this.image = $("<img src='" + this.settings.image + "' style='display: none'>");
            };
            snows.push(this);
        }, Snow.prototype.resetFlake = function(flake) {
            if (0 == this.settings.windPower || 0 == this.settings.windPower)
                flake.x = Math.floor(Math.random() * this.canvas.width), flake.y = 0;
            else if (this.settings.windPower > 0) {
                var xarray = Array(Math.floor(Math.random() * this.canvas.width), 0),
                    yarray = Array(0, Math.floor(Math.random() * this.canvas.height)),
                    allarray = Array(xarray, yarray),
                    selected_array = allarray[Math.floor(Math.random() * allarray.length)];
                flake.x = selected_array[0], flake.y = selected_array[1]
            } else {
                var xarray = Array(Math.floor(Math.random() * this.canvas.width), 0),
                    yarray = Array(this.canvas.width, Math.floor(Math.random() * this.canvas.height)),
                    allarray = Array(xarray, yarray),
                    selected_array = allarray[Math.floor(Math.random() * allarray.length)];
                flake.x = selected_array[0], flake.y = selected_array[1]
            }
            flake.size = Math.floor(100 * Math.random()) % this.settings.size + 2,
                flake.speed = Math.floor(100 * Math.random()) % this.settings.speed + Math.random() * flake.size / 10 + .5,
                flake.velY = flake.speed, flake.velX = 0, flake.opacity = .5 * Math.random() + this.settings.opacity
        };
        function starSnow(){
            var render = function() {
                for(var index = 0 , thiz ; thiz = snows[index++];){
                    thiz.ctx.clearRect(0, 0, thiz.canvas.width, thiz.canvas.height);
                    for (var i = 0; i < thiz.flakeCount; i++) {
                        var flake = thiz.flakes[i];
                        switch (flake.velX *= .98, flake.velY <= flake.speed && (flake.velY = flake.speed), thiz.settings.windPower) {
                            case !1:
                                flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
                                break;
                            case 0:
                                flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
                                break;
                            default:
                                flake.velX += .01 + thiz.settings.windPower / 100
                        }
                        if (flake.y += flake.velY, flake.x += flake.velX, (flake.y >= thiz.canvas.height || flake.y <= 0) && thiz.resetFlake(flake), (flake.x >= thiz.canvas.width || flake.x <= 0) && thiz.resetFlake(flake), 0 == thiz.settings.image) {
                            var grd = thiz.ctx.createRadialGradient(flake.x, flake.y, 0, flake.x, flake.y, flake.size - 1);
                            grd.addColorStop(0, thiz.settings.startColor), grd.addColorStop(1, thiz.settings.endColor), thiz.ctx.fillStyle = grd, thiz.ctx.beginPath(), thiz.ctx.arc(flake.x, flake.y, flake.size, 0, 2 * Math.PI), thiz.ctx.fill()
                        } else
                            thiz.ctx.drawImage(thiz.image[0], flake.x, flake.y, 2 * flake.size, 2 * flake.size)
                    }
                }
                window.requestAnimationFrame(render);
            };
            render()
        }
        $.fn.snow = function() {
            var userCanvas = supportCanvas();
            userCanvas && $(this).each(function(i, e) {
                var scope = {};
                $.each(e.attributes, function(index, key) {
                    scope[$.camelCase(key.name)] = Number(Number(key.value)) ? Number(key.value) : key.value
                });
                if(scope.image === "false") scope.image = false;
                new Snow($(e), {
                    speed: scope.speed || 1,
                    size: scope.size || 2,
                    count: scope.count || 200,
                    opacity: scope.opacity || 1,
                    startColor: scope.startColor || "rgba(255,255,255,1)",
                    endColor: scope.endColor || "rgba(255,255,255,0)",
                    windPower: scope.windPower || 0,
                    image: scope.image || !1
                });
            });
            starSnow();
        };
    })(Zepto , window);

除了去掉一些兼容处理,其它的改动不大,就是多建立了个snows的数组,新建的Snow对象都会放大这个数组里面去,然后就是去掉了Snow的原型方法snow(),时间戳处理统一在startSnow()函数中处理,在函数内部遍历snows数组,改了后效果是一样的,不过在手机中卡顿比之前强了不少。

 

posted @ 2015-12-27 14:27  骨月枫🍁  阅读(1512)  评论(0编辑  收藏  举报