jparticles
Version:
A lightweight, efficient and easy-to-use Canvas library for building some cool particle effects.
399 lines (398 loc) • 16.2 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var constants_1 = require("./common/constants");
var shape_1 = __importDefault(require("./common/shape"));
var utils_1 = require("./utils/index");
var Particle = /** @class */ (function (_super) {
__extends(Particle, _super);
function Particle(selector, options) {
var _this = _super.call(this, Particle.defaultConfig, selector, options) || this;
// 鼠标坐标 X
_this.mouseX = 0;
// 鼠标坐标 Y
_this.mouseY = 0;
_this.bootstrap();
return _this;
}
/**
* 初始化数据和运行程序
*/
Particle.prototype.init = function () {
this.ownResizeEvent();
this.optionsNormalize();
if (this.options.range > 0) {
// 定位点坐标
this.positionX = Math.random() * this.canvasWidth;
this.positionY = Math.random() * this.canvasHeight;
this.defineLineShape();
this.positionEvent();
}
// 初始化鼠标在视差上的坐标
this.mouseX = this.mouseY = 0;
this.parallaxEvent();
// 创建粒子
this.createDots();
};
/**
* 标准化配置参数,参考 calcQuantity 方法描述。
* 如:
* num: 0.5 => 表示 0.5 倍画布宽度 => 标准化为具体数值,如 100
* num: 100 => 表示具体数值 => 标准化结果还是 100
*/
Particle.prototype.optionsNormalize = function () {
var _a = this, canvasWidth = _a.canvasWidth, options = _a.options;
var props = ['num', 'proximity', 'range'];
props.forEach(function (prop) {
options[prop] = utils_1.pInt(utils_1.calcQuantity(options[prop], canvasWidth));
});
// 设置触发事件的元素
if (!utils_1.isElement(options.eventElem) && options.eventElem !== document) {
options.eventElem = this.canvas;
}
};
/**
* 根据配置参数生成对应形状的连线函数
*/
Particle.prototype.defineLineShape = function () {
var _this = this;
var _a = this.options, proximity = _a.proximity, range = _a.range, lineShape = _a.lineShape;
switch (lineShape) {
case 'cube':
this.lineShapeMaker = function (x, y, sx, sy, cb) {
var _a = _this, positionX = _a.positionX, positionY = _a.positionY;
if (Math.abs(x - sx) <= proximity &&
Math.abs(y - sy) <= proximity &&
Math.abs(x - positionX) <= range &&
Math.abs(y - positionY) <= range &&
Math.abs(sx - positionX) <= range &&
Math.abs(sy - positionY) <= range) {
cb();
}
};
break;
default:
this.lineShapeMaker = function (x, y, sx, sy, cb) {
var _a = _this, positionX = _a.positionX, positionY = _a.positionY;
if (Math.abs(x - sx) <= proximity &&
Math.abs(y - sy) <= proximity &&
((Math.abs(x - positionX) <= range &&
Math.abs(y - positionY) <= range) ||
(Math.abs(sx - positionX) <= range &&
Math.abs(sy - positionY) <= range))) {
cb();
}
};
}
};
/**
* 根据配置参数创建许多粒子(纯数据)
* 最后通过 draw 函数绘制真实可见的图形
*/
Particle.prototype.createDots = function () {
var _a = this, canvasWidth = _a.canvasWidth, canvasHeight = _a.canvasHeight, getColor = _a.getColor;
var _b = this.options, maxR = _b.maxR, minR = _b.minR, maxSpeed = _b.maxSpeed, minSpeed = _b.minSpeed, parallaxLayer = _b.parallaxLayer, spin = _b.spin, spinMaxSpeed = _b.spinMaxSpeed, spinMinSpeed = _b.spinMinSpeed;
var layerLength = parallaxLayer.length;
var num = this.options.num;
while (num--) {
var r = utils_1.randomInRange(maxR, minR);
this.elements.push({
r: r,
x: utils_1.randomInRange(canvasWidth - r, r),
y: utils_1.randomInRange(canvasHeight - r, r),
vx: utils_1.randomSpeed(maxSpeed, minSpeed),
vy: utils_1.randomSpeed(maxSpeed, minSpeed),
color: getColor(),
shape: this.getShapeData(),
// 定义粒子在视差图层里的层数及每层的层级大小
parallaxLayer: parallaxLayer[Math.floor(Math.random() * layerLength)],
// 定义粒子视差的偏移值
parallaxOffsetX: 0,
parallaxOffsetY: 0,
// 定义粒子的旋转角度
rotate: spin ? utils_1.randomInRange(0, 360) : 0,
// 粒子的旋转速度
rotateSpeed: utils_1.randomSpeed(spinMaxSpeed, spinMinSpeed),
});
}
};
/**
* 绘制粒子
*/
Particle.prototype.draw = function () {
var _this = this;
var ctx = this.ctx;
var lineWidth = this.options.lineWidth;
this.clearCanvasAndSetGlobalAttrs();
// 当 canvas 宽高改变的时候,全局属性需要重新设置
ctx.lineWidth = lineWidth;
// 更新粒子坐标
this.updateXY();
// 绘制粒子
this.elements.forEach(function (dot) {
var x = dot.x, y = dot.y, parallaxOffsetX = dot.parallaxOffsetX, parallaxOffsetY = dot.parallaxOffsetY;
// 更新粒子旋转角度
_this.updateElementRotate(dot);
_this.drawShape(__assign(__assign({}, dot), { x: x + parallaxOffsetX, y: y + parallaxOffsetY }));
});
// 连接粒子
this.connectDots();
// 循环绘制
this.requestAnimationFrame();
};
/**
* 更新元素自旋数据
*/
Particle.prototype.updateElementRotate = function (element) {
if (!this.options.spin || this.isPaused) {
return;
}
// 更新旋转角度
element.rotate += element.rotateSpeed;
// 大于等于 360 度时,回到 0-360 范围,避免变量数值一直累计超过 MAX_SAFE_INTEGER
if (element.rotate >= 360) {
element.rotate = element.rotate - 360;
}
};
/**
* 连接粒子,绘制线段
*/
Particle.prototype.connectDots = function () {
// 当连接范围小于 0 时,不连接线段
if (this.options.range <= 0)
return;
var _a = this, elements = _a.elements, ctx = _a.ctx, lineShapeMaker = _a.lineShapeMaker;
var length = elements.length;
elements.forEach(function (dot, i) {
var x = dot.x + dot.parallaxOffsetX;
var y = dot.y + dot.parallaxOffsetY;
var _loop_1 = function () {
var sibDot = elements[i];
var sx = sibDot.x + sibDot.parallaxOffsetX;
var sy = sibDot.y + sibDot.parallaxOffsetY;
lineShapeMaker === null || lineShapeMaker === void 0 ? void 0 : lineShapeMaker(x, y, sx, sy, function () {
ctx.save();
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(sx, sy);
ctx.strokeStyle = dot.color;
ctx.stroke();
ctx.restore();
});
};
while (++i < length) {
_loop_1();
}
});
};
/**
* 更新粒子坐标
*/
Particle.prototype.updateXY = function () {
var _a = this, isPaused = _a.isPaused, mouseX = _a.mouseX, mouseY = _a.mouseY, canvasWidth = _a.canvasWidth, canvasHeight = _a.canvasHeight;
var _b = this.options, parallax = _b.parallax, parallaxStrength = _b.parallaxStrength;
// 暂停的时候,vx 和 vy 保持不变,
// 防止自适应窗口变化时出现粒子移动
if (isPaused)
return;
this.elements.forEach(function (dot) {
if (parallax) {
// https://github.com/jnicol/particleground/blob/master/jquery.particleground.js#L279-L282
var divisor = parallaxStrength * dot.parallaxLayer;
dot.parallaxOffsetX += (mouseX / divisor - dot.parallaxOffsetX) / 10;
dot.parallaxOffsetY += (mouseY / divisor - dot.parallaxOffsetY) / 10;
}
dot.x += dot.vx;
dot.y += dot.vy;
var r = dot.r, parallaxOffsetX = dot.parallaxOffsetX, parallaxOffsetY = dot.parallaxOffsetY;
var x = dot.x, y = dot.y;
x += parallaxOffsetX;
y += parallaxOffsetY;
// 自然碰撞反向,视差事件移动反向
if (x + r >= canvasWidth) {
dot.vx = -Math.abs(dot.vx);
}
else if (x - r <= 0) {
dot.vx = Math.abs(dot.vx);
}
if (y + r >= canvasHeight) {
dot.vy = -Math.abs(dot.vy);
}
else if (y - r <= 0) {
dot.vy = Math.abs(dot.vy);
}
});
};
/**
* 获取绑定的 DOM 元素(eventElem)的 offset 值
*/
Particle.prototype.getEventElemOffset = function () {
var eventElem = this.options.eventElem;
return eventElem === document ? null : utils_1.offset(eventElem);
};
/**
* 事件代理
* @param move 移动事件处理函数
* @param orientation 陀螺仪事件处理函数
*/
Particle.prototype.eventProxy = function (move, orientation) {
var _this = this;
var eventElem = this.options.eventElem;
var handleOrientation;
if (constants_1.orientationSupport) {
handleOrientation = function (e) {
if (_this.isPaused || utils_1.isNull(e.beta))
return;
// 转换 beta 范围 [-180, 180] 成 [-90, 90]
orientation(Math.min(Math.max(e.beta, -90), 90), e.gamma);
};
window.addEventListener('deviceorientation', handleOrientation);
}
var handleMove = function (e) {
if (_this.isPaused)
return;
var left = e.pageX;
var top = e.pageY;
var offset = _this.getEventElemOffset();
if (offset) {
left -= offset.left;
top -= offset.top;
}
move(left, top);
};
eventElem.addEventListener('mousemove', handleMove);
// 实例销毁时移除绑定的事件
this.onDestroy(function () {
window.removeEventListener('deviceorientation', handleOrientation);
eventElem.removeEventListener('mousemove', handleMove);
});
};
/**
* 鼠标位置事件,根据鼠标的坐标将范围内的粒子连接起来
*/
Particle.prototype.positionEvent = function () {
var _this = this;
var range = this.options.range;
// 性能优化
if (range > this.canvasWidth && range > this.canvasHeight)
return;
this.eventProxy(
// 鼠标移动事件
function (left, top) {
_this.positionX = left;
_this.positionY = top;
},
// 陀螺仪事件
function (beta, gamma) {
_this.positionX = (-(gamma - 90) / 180) * _this.canvasWidth;
_this.positionY = (-(beta - 90) / 180) * _this.canvasHeight;
});
};
/**
* 视差效果事件
*/
Particle.prototype.parallaxEvent = function () {
var _this = this;
if (!this.options.parallax)
return;
this.eventProxy(function (left, top) {
_this.mouseX = left - _this.canvasWidth / 2;
_this.mouseY = top - _this.canvasHeight / 2;
}, function (beta, gamma) {
// 一半高度或宽度的对应比例值
// mouseX: - gamma / 90 * canvasWidth / 2;
// mouseY: - beta / 90 * canvasHeight / 2;
_this.mouseX = (-gamma * _this.canvasWidth) / 180;
_this.mouseY = (-beta * _this.canvasHeight) / 180;
});
};
/**
* 窗口尺寸调整事件
*/
Particle.prototype.ownResizeEvent = function () {
var _this = this;
this.onResize(function (scaleX, scaleY) {
if (_this.options.range > 0) {
_this.positionX *= scaleX;
_this.positionY *= scaleY;
_this.mouseX *= scaleX;
_this.mouseY *= scaleY;
}
});
};
Particle.defaultConfig = {
// 粒子个数,默认为容器宽度的 0.12 倍
// (0, 1) 显示为容器宽度相应倍数的个数,0 & [1, +∞) 显示具体个数
// 0 是没有意义的
num: 0.12,
// 粒子最大半径(0, +∞)
maxR: 2.4,
// 粒子最小半径(0, +∞)
minR: 0.6,
// 粒子最大运动速度(0, +∞)
maxSpeed: 1,
// 粒子最小运动速度(0, +∞)
minSpeed: 0.1,
// 两点连线的最大值
// 在 range 范围内的两点距离小于 proximity,则两点之间连线
// (0, 1) 显示为容器宽度相应倍数的个数,0 & [1, +∞) 显示具体个数
proximity: 0.2,
// 定位点的范围,范围越大连线越多
// 当 range 等于 0 时,不连线,相关值无效
// (0, 1) 显示为容器宽度相应倍数的个数,0 & [1, +∞) 显示具体个数
range: 0.2,
// 线段的宽度
lineWidth: 0.2,
// 连线的形状
// spider: 散开的蜘蛛状
// cube: 合拢的立方体状
lineShape: 'spider',
// 改变定位点坐标的事件元素
// null 表示 canvas 画布,或传入原生元素对象,如 document 等
eventElem: null,
// 视差效果 {boolean}
parallax: false,
// 定义粒子在视差图层里的层数及每层的层级大小,类似 css 里的 z-index。
// 取值范围: [0, +∞),值越小视差效果越强烈,0 则不动。
// 定义四层粒子示例:[1, 3, 5, 10]
parallaxLayer: [1, 2, 3],
// 视差强度,值越小视差效果越强烈
parallaxStrength: 3,
// 是否自旋
spin: false,
// 粒子最大运动角速度(0, 360)
spinMaxSpeed: 5,
// 粒子最小运动角速度(0, 360)
spinMinSpeed: 1,
};
return Particle;
}(shape_1.default));
exports.default = Particle;