diff --git a/README.md b/README.md index 767349a..fc00f3f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,49 @@ # particle -HTML5中,采用JavaScript和Canvas实现运动粒子效果,兼容IE10+,chrome、safari等。 \ No newline at end of file +HTML5中,采用JavaScript和Canvas实现运动粒子效果,兼容IE10+,chrome、safari等。 + +本项目基于 [https://github.com/AnCIity/ui-login-canvas](https://github.com/AnCIity/ui-login-canvas) 修改 + +![预览图](preview.gif) + +用法: +```javascript +var particle = new Particle({ + canvas: document.getElementById('canvas'), + width: 600, + height: 400, + ballNumber: 100, + ballMinSize: 1, + ballMaxSize: 5, + fps: 24, + color: 'rgba(84, 23, 196, 0.75)' + }); + +particle.reDraw({ + width: 600, + height: 400, + ballNumber: 100, + ballMinSize: 1, + ballMaxSize: 5, + color: 'rgba(84, 23, 196, 0.75)' +}); +``` +方法: + +| 方法名 | 方法数说明 | +| ------ | ------ | +|new Particle(options)|实例化粒子动画对象| +|instance.reDraw(options)|重绘粒子动画,此方法未new Particle()的实例对象上的方法| + +参数options说明: + +| 参数名 | 参数类型 | 参数说明 | +| ------ | ------ | ------ | +| canvas | HTMLCanvasElement | 即通过原生js获取到的canvas对象 | +| width | number | 粒子效果宽度,不设为自动获取canvas宽度 | +| height | number | 粒子效果高度,不设为自动获取canvas高度 | +| ballNumber | number | 粒子个数,越大越多,越多相对越卡 | +| ballMinSize | number | 粒子最小尺寸,粒子尺寸减少到此值后,反向缓慢增加到最大尺寸 | +| ballMaxSize | number | 粒子最大尺寸,粒子尺寸增加到此值后,反向缓慢减少到最小尺寸 | +| fps | number | 动画帧数,一半设为24就可以了 | +| color | string 或 string array | 粒子颜色,可为颜色字符串或颜色字符串数组,若为字符串数组,则为\[起始色,终止色],粒子颜色会在该范围内改变 | diff --git a/index.html b/index.html new file mode 100755 index 0000000..11bd07b --- /dev/null +++ b/index.html @@ -0,0 +1,40 @@ + + + + + + 粒子效果 + + + + + + + + diff --git a/js/particle.js b/js/particle.js new file mode 100755 index 0000000..534c4a1 --- /dev/null +++ b/js/particle.js @@ -0,0 +1,299 @@ +; +/* + * 原项目地址:https://github.com/AnCIity/ui-login-canvas + * 原作者:AnCity + * 修改:yhl452493373 + * 不依赖外部库,仅使用canvas + */ +(function () { + /** + * 生成颜色范围内随机色 + * https://stackoverflow.com/questions/25193110/random-color-between-two-selected-rgb-colors-javascript 最下方答案 + * @param color1 颜色1 + * @param color2 颜色2 + * @private + */ + var randomColor = function (color1, color2) { + this.regs = { + "hex3": /^#([a-f\d])([a-f\d])([a-f\d])$/i, + "hex6": /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i, + "rgb": /^rgb\s*\(\s*([\d.]+%?)\s*,\s*([\d.]+%?)\s*,\s*([\d.]+%?)\s*\)$/ + }; + + this.colorObject1 = this.getValues(color1); + this.colorObject2 = this.getValues(color2); + } + + /** + * 将传颜色字符串转为带rgb数值的对象 + * @param color 颜色 + * @returns 转换结果,若为false,则转换失败,说明颜色字符串错误 + */ + randomColor.prototype.getValues = function (color) { + var values = false; + for (var prop in this.regs) { + // noinspection JSUnfilteredForInLoop + if (this.regs[prop].test(color)) { + values = {}; + // noinspection JSUnfilteredForInLoop + values.r = color.replace(this.regs[prop], "$1"); + // noinspection JSUnfilteredForInLoop + values.g = color.replace(this.regs[prop], "$2"); + // noinspection JSUnfilteredForInLoop + values.b = color.replace(this.regs[prop], "$3"); + if (prop === "rgb") { + values.r = Number(values.r); + values.g = Number(values.g); + values.b = Number(values.b); + } else { + values.r = parseInt(values.r, 16); + values.g = parseInt(values.g, 16); + values.b = parseInt(values.b, 16); + } + break; + } + } + return values; + } + + /** + * 生成随机色 + * @returns {string|boolean} + */ + randomColor.prototype.getColor = function () { + if (this.colorObject1 && this.colorObject2) { + var random = Math.random(); + var r = randomColor.getRandom(this.colorObject1.r, this.colorObject2.r, random); + var g = randomColor.getRandom(this.colorObject1.g, this.colorObject2.g, random); + var b = randomColor.getRandom(this.colorObject1.b, this.colorObject2.b, random); + return "#" + r + g + b; + } + return false; + }; + + /** + * 字符串补位,如将a,左侧补位变为0a + * + * @param str 需要补位的字符串 + * @param pad_length 补位后字符串的长度 + * @param pad_string 用于补位的字符串 + * @param pad_type 补位方式 + * @returns {string|*} + * @private + */ + randomColor.str_pad = function (str, pad_length, pad_string, pad_type) { + var len = pad_length - str.length; + if (len < 0) { + return str + } + var pad = new Array(len + 1).join(pad_string); + if (pad_type === "STR_PAD_LEFT") { + return pad + str + } + return str + pad; + } + + /** + * 获取R,G,B其中之一随机色范围 + * @param color1 颜色1 R,G,B其中之一 + * @param color2 颜色2 R,G,B其中之一 + * @param percent 随机到的范围百分比 + * @returns {string|*} + * @private + */ + randomColor.getRandom = function (color1, color2, percent) { + var color = color1 + Math.floor((color2 - color1) * percent); + if (color < 0) + color = 0; + return randomColor.str_pad(color.toString(16), 2, "0", "STR_PAD_LEFT"); + } + + /** + * 定义插件对象 + * + * @param options {{ + * canvas:HTMLCanvasElement + * width:number?, + * height:number?, + * ballNumber:number, + * ballMinSize:number, + * ballMaxSize:number, + * fps:number, + * color:string|[string,string] + * }} + */ + var particle = function (options) { + this.ball = []; + this.canvas = options.canvas; + if (this.canvas.style.width !== '' || this.canvas.style.height !== '') { + throw new Error('请勿在粒子动效的canvas上的style中添加width,仅允许通过参数传递') + } else if (this.canvas.getAttribute('width') != null || this.canvas.getAttribute('height') != null) { + throw new Error('请勿在粒子动效的canvas上的添加width、height属性,仅允许通过参数传递') + } + this.canvas.width = options.width || parseInt(getComputedStyle(this.canvas).width); + this.canvas.height = options.height || parseInt(getComputedStyle(this.canvas).height); + this.canvas.style.width = this.canvas.width + 'px'; + this.canvas.style.height = this.canvas.height + 'px'; + this.context = this.canvas.getContext('2d'); + this.ballNumber = options.ballNumber + this.ballMinSize = options.ballMinSize; + this.ballMaxSize = options.ballMaxSize; + this.color = options.color; + if (this.color instanceof Array) { + if (this.color.length > 1) + this.randomColor = new randomColor(this.color[0], this.color[1]); + else + this.color = options.color[0]; + } + var that = this; + this.__draw(true); + self.setInterval(function () { + that.__draw(false); + }, 1000 / options.fps); + }; + + /** + * 插件重设方法 + * + * @param options {{ + * width:number?, + * height:number?, + * ballNumber:number?, + * ballMinSize:number?, + * ballMaxSize:number?, + * color:string? + * }} + */ + particle.prototype.reDraw = function (options) { + if (options) { + if (options.hasOwnProperty('width')) { + this.canvas.width = options.width; + this.canvas.style.width = this.canvas.width+'px'; + } + if (options.hasOwnProperty('height')) { + this.canvas.height = options.height; + this.canvas.style.height = this.canvas.height+'px'; + } + if (options.hasOwnProperty('ballNumber')) + this.ballNumber = options.ballNumber; + if (options.hasOwnProperty('ballMinSize')) + this.ballMinSize = options.ballMinSize; + if (options.hasOwnProperty('ballMaxSize')) + this.ballMaxSize = options.ballMaxSize; + if (options.hasOwnProperty('color')) { + this.color = options.color; + if (this.color instanceof Array) { + if (this.color.length > 0) + this.randomColor = new randomColor(this.color[0], this.color[1]); + else + this.color = options.color[0]; + } + } + this.__draw(true); + } else { + this.canvas.style.width = ''; + this.canvas.style.height = ''; + this.canvas.width=''; + this.canvas.height=''; + this.canvas.width = parseInt(getComputedStyle(this.canvas).width); + this.canvas.height = parseInt(getComputedStyle(this.canvas).height); + this.canvas.style.width = this.canvas.width+'px'; + this.canvas.style.height = this.canvas.height+'px'; + this.__draw(false); + } + } + + /** + * 绘制点 + * @param force 是否强制绘制。为true时,会清空并重新生成所有点并重新绘制点,用于初始化;为false时,不会重新生成点,会清空并重绘点 + * @private + */ + particle.prototype.__draw = function (force) { + //初始化小球数据 + if (force) { + this.ball = []; + this.__ballConfigure(); + } + + //重绘canvas + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + var i = 0; + while (i < this.ballNumber) { + //绘制小球 + this.context.beginPath(); + if (this.ball[i].size > 0) + this.context.arc(this.ball[i].x, this.ball[i].y, this.ball[i].size, 0, Math.PI * 2, true); + this.context.closePath(); + this.context.fillStyle = this.ball[i].color; + this.context.fill(); + //赋予小球加速度 + this.ball[i].x += this.ball[i].vx; + this.ball[i].y += this.ball[i].vy; + this.ball[i].size -= this.ball[i].sizeReduce; + if (this.ball[i].size < this.ballMinSize || this.ball[i].size <= 0) { + this.ball[i].size = this.ballMinSize; + this.ball[i].sizeReduce *= -1; + this.ball[i].color = this.__randomColor(); + } else if (this.ball[i].size > this.ballMaxSize) { + this.ball[i].size = this.ballMaxSize; + this.ball[i].sizeReduce *= -1; + } + //防止小球出界 + if (this.ball[i].x <= 0 || this.ball[i].x >= this.canvas.width) { + this.ball[i].vx = -this.ball[i].vx; + } + if (this.ball[i].y <= 0 || this.ball[i].y >= this.canvas.height) { + this.ball[i].vy = -this.ball[i].vy; + } + i++; + } + }; + + /** + * 根据配置生成小球并放入数组 + * @private + */ + particle.prototype.__ballConfigure = function () { + //获取随机小球配置 + var j = 0; + while (j < this.ballNumber) { + this.ball[j] = { + x: Math.ceil(Math.random() * this.canvas.width), + y: Math.ceil(Math.random() * this.canvas.height), + vx: particle.__noZeroRandom(-1, 1), + vy: particle.__noZeroRandom(-1, 1), + size: particle.__noZeroRandom(this.ballMinSize, this.ballMaxSize), + color: this.__randomColor(), + sizeReduce: 0.1 + }; + j++; + } + }; + + /** + * 利用 randomColor 生成范围随机色,如果没有范围,则返回指定色 + * + * @returns {*|string|string[]} + */ + particle.prototype.__randomColor = function () { + return this.color instanceof Array ? this.randomColor.getColor() : this.color; + } + + /** + * 生成不为0随机数,直接挂载到对象上,而非实例上 + * @param min 最小 + * @param max 最大 + * @returns {number} 生成值 + * @private + */ + particle.__noZeroRandom = function (min, max) { + var k = 0; + while (k === 0) { + k = Math.floor(Math.random() * (max - min + 1) + min); + } + return k; + } + + + window.Particle = particle; +})(); \ No newline at end of file diff --git a/preview.gif b/preview.gif new file mode 100644 index 0000000..55a12e7 Binary files /dev/null and b/preview.gif differ