一、實作效果
❤ ❤分享一個WEB前端canvas滑鼠滑過星空背景特效超好看❤ ❤一、實作效果二、代碼 二、代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>canvas--again</title>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
html,body {
width: 100%;
height: 100%;
/* background: linear-gradient(to top, palevioletred 0%, #fff 100%); */
background: no-repeat linear-gradient(to bottom, #011a39 0%, #009d7f 100%);
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script>
(function(win, doc) {
const maxW = win.innerWidth;
const maxH = win.innerHeight;
const maxSize = 5;
const minSize = 1;
let isMoving = false;
let timer = null;
let canvas = doc.getElementById('canvas');
canvas.width = maxW;
canvas.height = maxH;
let ctx = canvas.getContext('2d');
let stars = []; // 存放作為背景使用的星星
let move_stars = []; // 存放滑鼠移動時繪制的星星
function CanvasStar(num) {
this.num = num;
};
CanvasStar.prototype = {
render() {
for (let i = 0; i < this.num; i++) {
let alpha = Math.random() + 0.1;
stars[i] = new Star(i, getOneRandom(maxW), getOneRandom(maxH), getOneRandom(maxSize, minSize), true, alpha);
}
animate();
}
};
function Star(id, x, y, r, isCache, dot_alpha) {
this.id = id;
this.x = x;
this.y = y;
this.r = r;
this.dot_alpha = dot_alpha;
this.show = .5; // 作用:控制 滑鼠繪制點的隐藏
this.direct = getOneRandom(180) + 180;
this.isCache = isCache;
this.cacheCanvas = doc.createElement('canvas');
this.ctx_cache = this.cacheCanvas.getContext('2d');
if(isCache) {
this.cache();
}
};
Star.prototype = {
draw() {
// 繪制一個Star
if(!this.isCache) {
let alpha = Math.random() + 0.1;
ctx.save();
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.shadowColor = '#fff';
ctx.shadowBlur = 2 * this.r;
ctx.fillStyle = `rgba(255, 255, 255, ${ this.dot_alpha })`;
ctx.fill();
ctx.restore();
}else {
ctx.drawImage(this.cacheCanvas, this.x - 3 * this.r, this.y - 3 * this.r)
}
},
move() {
this.y -= 0.25;
if(this.y < -10) {
this.y = maxH + 10;
}
this.draw();
},
// 使滑鼠繪制的點抖動起來
shake(arr) {
this.show -= 0.01;
if(this.show < 0) {
return;
}
let speed = .5;
this.x += Math.cos(this.direct * Math.PI / 180) * speed;
this.y += Math.sin(this.direct * Math.PI / 180) * speed;
this.draw();
this.link();
},
link() {
if(!this.id) return;
let len = move_stars.length;
// 關鍵思想:取目前id,之前的4個點,每繪制一次就向前取4個點,以此類推
// 而不是move_stars最後的四個點,否則的話隻有最後幾個點會被連接配接起來
let arr = move_stars.slice(this.id - 3, this.id);
let endIdx = arr.length - 1;
ctx.save();
for(let i = endIdx; i >= 0; i--) {
if(i == endIdx && !!arr[endIdx]) {
ctx.moveTo(arr[endIdx].x, arr[endIdx].y);
ctx.beginPath();
ctx.lineTo(this.x, this.y);
}
if(i != endIdx && !!arr[i] && arr[i].show > 0) ctx.lineTo(arr[i].x, arr[i].y);
}
ctx.closePath();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.125)';
ctx.stroke();
ctx.restore();
},
cache() {
this.cacheCanvas.width = 6 * this.r;
this.cacheCanvas.height = 6 * this.r;
this.ctx_cache.save();
this.ctx_cache.beginPath();
this.ctx_cache.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI, false);
this.ctx_cache.closePath();
this.ctx_cache.shadowColor = '#fff';
this.ctx_cache.shadowBlur = 2 * this.r;
this.ctx_cache.fillStyle = `rgba(255, 255, 255, ${this.dot_alpha})`;
this.ctx_cache.fill();
this.ctx_cache.restore();
}
};
// 動畫
function animate() {
ctx.clearRect(0, 0, maxW, maxH);
let len = stars.length;
for(let i = 0; i < len; i++) {
stars[i].move();
}
let len2 = move_stars.length;
if(isMoving) {
for(let i = 0; i < len2; i++) {
if(move_stars[i]) move_stars[i].shake(move_stars);
}
}else {
move_stars = []
}
requestAnimationFrame(animate);
};
// 擷取區間内的随機數
function getOneRandom(max, min = 0) {
return Math.floor(Math.random() * (max - min) + min);
};
// 擷取正負号
function getSign() {
return Math.random() > .5 ? -1 : 1;
};
// 滑鼠移動事件
doc.addEventListener('mousemove', function(e) {
let x = e.clientX, y = e.clientY;
// 控制繪制密度 以及點之間的距離 兩個重要的點
// 密度是控制繪制的數量 dis_x = Math.abs(x - pre_star.x);
// 距離是在已繪制的點基礎上、控制點的間距
// 控制繪制密度 和 控制點之間的距離 不是一個功能哦(需要實際操作去體會, 文字很難表述~.~)
// 沒有控制距離的話 繪制的圖形,太規則了,不夠分散 x = x + getSign() * getOneRandom(50)
let len = move_stars.length;
let dis_x, dis_y;
if(!len) {
move_stars.push( new Star(len, x, y, getOneRandom(maxSize, 3), true, 1) );
}
if(move_stars[len - 1]) {
// 目前的星還沒有push到move_stars裡,是以上個星是move_stars[len - 1]
let pre_star = move_stars[len - 1];
if(pre_star) {
dis_x = Math.abs(x - pre_star.x);
dis_y = Math.abs(y - pre_star.y);
}
x = x + getSign() * getOneRandom(50);
y = y + getSign() * getOneRandom(50);
if(dis_x > 5 && dis_y > 5) move_stars.push( new Star(len, x, y, getOneRandom(maxSize, minSize), true, 1) );
}
isMoving = true;
clearInterval(timer); // 清除上一次的定時器(此時還沒觸發)
timer = setInterval(function() {
isMoving = false;
clearInterval(timer); // 滑鼠停止再清除下定時器
}, 500)
}, false);
window.CanvasStar = CanvasStar;
})(window, document);
let canvasStar = new CanvasStar(200);
canvasStar.render();
</script>
<script>
/**小本本
* 疑問:
* 1. clientX、layerX、offsetX、pageX、screenX、x 的差別?
*/
</script>
</html>