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数组,改了后效果是一样的,不过在手机中卡顿比之前强了不少。