215 lines
7.4 KiB
JavaScript
215 lines
7.4 KiB
JavaScript
/**
|
||
* Starry Sky
|
||
*
|
||
* 作者: DoWake
|
||
* 描述:使用Canvas绘制星空
|
||
* 地址:https://github.com/DoWake/StarrySky
|
||
* 日期:2023/03/02
|
||
*/
|
||
|
||
const StarrySky = function () {
|
||
//Canvas元素
|
||
let canvasElement;
|
||
//Canvas 2D对象
|
||
let canvasContext;
|
||
//Canvas 宽度
|
||
let canvasWidth;
|
||
//Canvas 高度
|
||
let canvasHeight;
|
||
//星星列表
|
||
let starList;
|
||
//星星颜色列表,rgb格式:"255, 255, 255"
|
||
let starColorList;
|
||
//星星半径大小
|
||
let starRadius;
|
||
//焦距等级,与canvasWidth相乘,必须大于0
|
||
let focalDistanceLevel;
|
||
//星星数量等级,与canvasWidth相乘,必须大于0
|
||
let starCountLevel;
|
||
//星星速度等级,与焦距相乘,必须大于0
|
||
let starSpeedLevel;
|
||
//焦距
|
||
let focalDistance;
|
||
//星星数量
|
||
let starCount;
|
||
//执行动画
|
||
let rAF;
|
||
return {
|
||
//初始化
|
||
init: function (canvas_element) {
|
||
if (canvas_element && canvas_element.nodeName === "CANVAS") {
|
||
canvasElement = canvas_element;
|
||
canvasElement.width = canvasElement.clientWidth;
|
||
canvasElement.height = canvasElement.clientHeight;
|
||
canvasElement.style.backgroundColor = "black";
|
||
canvasContext = canvasElement.getContext("2d");
|
||
canvasWidth = canvasElement.clientWidth;
|
||
canvasHeight = canvasElement.clientHeight;
|
||
starColorList = ["255, 255, 255"];
|
||
starRadius = 1;
|
||
focalDistanceLevel = 0.4;
|
||
starCountLevel = 0.2;
|
||
starSpeedLevel = 0.0005;
|
||
focalDistance = canvasWidth * focalDistanceLevel;
|
||
starCount = Math.ceil(canvasWidth * starCountLevel);
|
||
starList = [];
|
||
for (let i = 0; i < starCount; i++) {
|
||
starList[i] = {
|
||
x: canvasWidth * (0.1 + 0.8 * Math.random()),
|
||
y: canvasHeight * (0.1 + 0.8 * Math.random()),
|
||
z: focalDistance * Math.random(),
|
||
color: starColorList[Math.ceil(Math.random() * 1000) % starColorList.length]
|
||
}
|
||
}
|
||
const self = this;
|
||
window.addEventListener("resize", self.throttle(function () {
|
||
canvasElement.width = canvasElement.clientWidth;
|
||
canvasElement.height = canvasElement.clientHeight;
|
||
canvasWidth = canvasElement.clientWidth;
|
||
canvasHeight = canvasElement.clientHeight;
|
||
focalDistance = canvasWidth * focalDistanceLevel;
|
||
|
||
const starCount2 = Math.ceil(canvasWidth * starCountLevel);
|
||
if (starCount > starCount2) {
|
||
starList.splice(starCount2);
|
||
} else {
|
||
let num = starCount2 - starCount;
|
||
while (num--) {
|
||
starList.push({
|
||
x: canvasWidth * (0.1 + 0.8 * Math.random()),
|
||
y: canvasHeight * (0.1 + 0.8 * Math.random()),
|
||
z: focalDistance * Math.random(),
|
||
color: starColorList[Math.ceil(Math.random() * 1000) % starColorList.length]
|
||
});
|
||
}
|
||
}
|
||
starCount = Math.ceil(canvasWidth * starCountLevel);
|
||
}, 200));
|
||
} else {
|
||
console.error('初始化失败,必须传入Canvas元素');
|
||
}
|
||
},
|
||
//设置星空背景颜色
|
||
setSkyColor: function (sky_color = "black") {
|
||
canvasElement.style.backgroundColor = sky_color;
|
||
},
|
||
//设置星星半径大小
|
||
setStarRadius: function (star_radius = 1) {
|
||
starRadius = star_radius;
|
||
},
|
||
//设置焦距等级
|
||
setFocalDistanceLevel: function (focal_distance_level = 0.4) {
|
||
focalDistanceLevel = focal_distance_level;
|
||
focalDistance = canvasWidth * focalDistanceLevel
|
||
},
|
||
//设置星星数量等级
|
||
setStarCountLevel: function (star_count_level = 0.2) {
|
||
starCountLevel = star_count_level;
|
||
const starCount2 = Math.ceil(canvasWidth * starCountLevel);
|
||
if (starCount > starCount2) {
|
||
starList.splice(starCount2);
|
||
} else {
|
||
let num = starCount2 - starCount;
|
||
while (num--) {
|
||
starList.push({
|
||
x: canvasWidth * (0.1 + 0.8 * Math.random()),
|
||
y: canvasHeight * (0.1 + 0.8 * Math.random()),
|
||
z: focalDistance * Math.random(),
|
||
color: starColorList[Math.ceil(Math.random() * 1000) % starColorList.length]
|
||
});
|
||
}
|
||
}
|
||
starCount = Math.ceil(canvasWidth * starCountLevel);
|
||
},
|
||
//设置星星速度等级
|
||
setStarSpeedLevel: function (star_speed_level = 0.0005) {
|
||
starSpeedLevel = star_speed_level
|
||
},
|
||
/**
|
||
* 设置星星颜色
|
||
* @param {Array|String} color 星星颜色
|
||
* @param {Boolean} mode 是否立刻同步颜色
|
||
*/
|
||
setStarColorList: function (color, mode = false) {
|
||
if (typeof color === 'object') {
|
||
starColorList = color;
|
||
} else if (typeof color === 'string') {
|
||
starColorList.push(color);
|
||
}
|
||
if (mode) {
|
||
for (let i = 0; i < starList.length; i++) {
|
||
starList[i]["color"] = starColorList[Math.ceil(Math.random() * 1000) % starColorList.length];
|
||
}
|
||
}
|
||
},
|
||
//渲染
|
||
render: function () {
|
||
const starSpeed = canvasWidth * focalDistanceLevel * starSpeedLevel;
|
||
//清空画布
|
||
canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
|
||
//计算位置
|
||
for (let i = 0; i < starList.length; i++) {
|
||
const star = starList[i];
|
||
const star_x = (star["x"] - canvasWidth / 2) * (focalDistance / star["z"]) + canvasWidth / 2;
|
||
const star_y = (star["y"] - canvasHeight / 2) * (focalDistance / star["z"]) + canvasHeight / 2;
|
||
star["z"] -= starSpeed;
|
||
if (star["z"] > 0 && star["z"] <= focalDistance && star_x >= -20 && star_x <= canvasWidth + 20 && star_y >= -20 && star_y <= canvasHeight + 20) {
|
||
const star_radius = starRadius * (focalDistance / star["z"] * 0.8);
|
||
const star_opacity = 1 - 0.8 * (star["z"] / focalDistance);
|
||
canvasContext.fillStyle = "rgba(" + star["color"] + ", " + star_opacity + ")";
|
||
canvasContext.shadowOffsetX = 0;
|
||
canvasContext.shadowOffsetY = 0;
|
||
canvasContext.shadowColor = "rgb(" + star["color"] + ")";
|
||
canvasContext.shadowBlur = 10;
|
||
canvasContext.beginPath();
|
||
canvasContext.arc(star_x, star_y, star_radius, 0, 2 * Math.PI);
|
||
canvasContext.fill();
|
||
} else {
|
||
const z = focalDistance * Math.random();
|
||
star["x"] = canvasWidth * (0.1 + 0.8 * Math.random());
|
||
star["y"] = canvasHeight * (0.1 + 0.8 * Math.random());
|
||
star["z"] = z;
|
||
star["color"] = starColorList[Math.ceil(Math.random() * 1000) % starColorList.length];
|
||
}
|
||
}
|
||
const self = this;
|
||
rAF = window.requestAnimationFrame(function () {
|
||
self.render();
|
||
});
|
||
},
|
||
//销毁
|
||
destroy: function () {
|
||
window.cancelAnimationFrame(rAF);
|
||
starList = [];
|
||
canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
|
||
canvasElement.width = 0;
|
||
canvasElement.height = 0;
|
||
},
|
||
//防抖
|
||
debounce: function (func, time = 200) {
|
||
let timeId;
|
||
return function () {
|
||
if (timeId) {
|
||
clearTimeout(timeId);
|
||
}
|
||
timeId = setTimeout(function () {
|
||
func();
|
||
}, time);
|
||
}
|
||
},
|
||
//节流
|
||
throttle: function (func, time = 200) {
|
||
let timeId = null;
|
||
let pre = 0;
|
||
return function () {
|
||
if (Date.now() - pre > time) {
|
||
clearTimeout(timeId);
|
||
pre = Date.now();
|
||
func();
|
||
} else {
|
||
timeId = setTimeout(func, time);
|
||
}
|
||
};
|
||
}
|
||
}
|
||
}(); |