Текст написанный лазерным лучом
Эффект лазерной гравировки любой текстовой надписи. Сделано на canvas.
HTML
<div class="page page-laser-to-text">
<input id="input" type="text" maxlength="24" placeholder="I love you!">
<canvas id="canvas"></canvas>
</div>
CSS
.page-laser-to-text {
position: relative;
overflow: hidden;
}
.page-laser-to-text canvas {
display: block;
}
.page-laser-to-text input {
position: absolute;
bottom: 50px;
left: 0;
right: 0;
display: block;
outline: none;
background-color: rgba(38, 50, 56, 0.2);
color: #ffffff;
border: none;
width: 50%;
min-width: 500px;
max-width: 100%;
margin: auto;
height: 60px;
line-height: 60px;
font-size: 40px;
padding: 0 20px;
}
.page-laser-to-text input:hover, .page-laser-to-text input:focus {
border: 1px solid rgba(38, 50, 56, 0.6);
}
.page-laser-to-text input::-webkit-input-placeholder {
color: rgba(255, 255, 255, 0.1);
}
JS
let canvas, ctx, w, h, laser, text, particles, input;
function Laser(options) {
options = options || {};
this.lifespan = options.lifespan || Math.round(Math.random() * 20 + 20);
this.maxlife = this.lifespan;
this.color = options.color || '#fd2423';
this.x = options.x || Math.random() * w;
this.y = options.y || Math.random() * h;
this.width = options.width || 2;
this.update = function(index, array) {
this.lifespan > 0 && this.lifespan--;
this.lifespan <= 0 && this.remove(index, array);
}
this.render = function(ctx) {
if (this.lifespan <= 0) return;
ctx.beginPath();
ctx.globalAlpha = this.lifespan / this.maxlife;
ctx.strokeStyle = this.color;
ctx.lineWidth = this.width;
ctx.moveTo(this.x, this.y);
ctx.lineTo(w, this.y);
ctx.stroke();
ctx.closePath();
}
this.remove = function(index, array) {
array.splice(index, 1);
}
}
function Spark(options) {
options = options || {};
this.x = options.x || w * 0.5;
this.y = options.y || h * 0.5;
this.v = options.v || { direct: Math.random() * Math.PI * 2, weight: Math.random() * 10 + 2, friction: 0.94 };
this.a = options.a || { change: Math.random() * 0.2 - 0.1, min: this.v.direct - Math.PI * 0.4, max: this.v.direct + Math.PI * 0.4 };
this.g = options.g || { direct: Math.PI * 0.5 + (Math.random() * 0.4 - 0.2), weight: Math.random() * 0.5 + 0.5 };
this.width = options.width || Math.random() * 3;
this.lifespan = options.lifespan || Math.round(Math.random() * 20 + 40);
this.maxlife = this.lifespan;
this.color = options.color || '#fdab23';
this.prev = { x: this.x, y: this.y };
this.update = function(index, array) {
this.prev = { x: this.x, y: this.y };
this.x += Math.cos(this.v.direct) * this.v.weight;
this.x += Math.cos(this.g.direct) * this.g.weight;
this.y += Math.sin(this.v.direct) * this.v.weight;
this.y += Math.sin(this.g.direct) * this.g.weight;
this.v.weight *= this.v.friction;
this.v.direct += this.a.change;
(this.v.direct > this.a.max || this.v.direct < this.a.min) && (this.a.change *= -1);
this.lifespan > 0 && this.lifespan--;
this.lifespan <= 0 && this.remove(index, array);
}
this.render = function(ctx) {
if (this.lifespan <= 0) return;
ctx.beginPath();
ctx.globalAlpha = this.lifespan / this.maxlife;
ctx.strokeStyle = this.color;
ctx.lineWidth = this.width;
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.prev.x, this.prev.y);
ctx.stroke();
ctx.closePath();
}
this.remove = function(index, array) {
array.splice(index, 1);
}
}
function Particles(options) {
options = options || {};
this.max = options.max || Math.round(Math.random() * 20 + 10);
this.sparks = [...new Array(this.max)].map(() => new Spark(options));
this.update = function() {
this.sparks.forEach((s, i) => s.update(i, this.sparks));
}
this.render = function(ctx) {
this.sparks.forEach(s => s.render(ctx));
}
}
function Text(options) {
options = options || {};
const pool = document.createElement('canvas');
const buffer = pool.getContext('2d');
pool.width = w;
buffer.fillStyle = '#000000';
buffer.fillRect(0, 0, pool.width, pool.height);
this.size = options.size || 100;
this.copy = (options.copy || `Hello!`) + ' ';
this.color = options.color || '#fecd96';
this.delay = options.delay || 4;
this.basedelay = this.delay;
buffer.font = `${this.size}px Comic Sans MS`;
this.bound = buffer.measureText(this.copy);
this.bound.height = this.size * 1.5;
this.x = options.x || w * 0.5 - this.bound.width * 0.5;
this.y = options.y || h * 0.5 - this.size * 0.5;
buffer.strokeStyle = this.color;
buffer.strokeText(this.copy, 0, this.bound.height * 0.8);
this.data = buffer.getImageData(0, 0, this.bound.width, this.bound.height);
this.index = 0;
this.update = function() {
if (this.index >= this.bound.width) {
this.index = 0;
return;
}
const data = this.data.data;
for (let i = this.index * 4; i < data.length; i += (4 * this.data.width)) {
const bitmap = data[i] + data[i + 1] + data[i + 2] + data[i + 3];
if (bitmap > 255 && Math.random() > 0.86) {
const x = this.x + this.index;
const y = this.y + (i / this.bound.width / 4);
laser.push(new Laser({
x: x,
y: y
}));
Math.random() > 0.7 && particles.push(new Particles({
x: x,
y: y
}));
}
}
this.delay-- < 0 && (this.index++ && (this.delay += this.basedelay));
}
this.render = function(ctx) {
ctx.putImageData(this.data, this.x, this.y, 0, 0, this.index, this.bound.height);
}
}
function loop() {
update();
render();
requestAnimationFrame(loop);
}
function update() {
text.update();
laser.forEach((l, i) => l.update(i, laser));
particles.forEach(p => p.update());
}
function render() {
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 0.9;
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, w, h);
//
ctx.globalCompositeOperation = 'screen';
text.render(ctx);
laser.forEach(l => l.render(ctx));
particles.forEach(p => p.render(ctx));
}
(function () {
//
canvas = document.getElementById('canvas');
input = document.getElementById('input');
ctx = canvas.getContext('2d');
w = window.innerWidth;
h = window.innerHeight;
canvas.width = w;
canvas.height = h;
laser = [];
particles = [];
//
text = new Text({
copy: 'Anh Yêu Em!'
});
canvas.addEventListener('click', (e) => {
const x = e.clientX;
const y = e.clientY;
laser.push(new Laser({
x: x,
y: y
}));
particles.push(new Particles({
x: x,
y: y
}));
});
let cb = 0;
input.addEventListener('keyup', (e) => {
clearTimeout(cb);
cb = setTimeout(() => {
text = new Text({
copy: input.value
});
}, 300);
});
//
loop();
})()