千千块模板 | 千千表白模板(七夕情人节必备神器)
七夕情人节就不必说什么了,没对象的大胆表白,有对象的送 ta 闺蜜一个惊喜
可以修改内容,修改的内容都在代码最上面


自定义块 js(祝你表白成功!)
// --- 可自定义区域 ---
const customTexts = {
rain: 'I LOVE U',
heart: '我会永远陪着你',
sequence: ['#countdown 3', '亲爱的', '节日快乐', '我爱你', '#rectangle'],
finalMessage: '祝你表白成功!' // 动画结束后显示在此处,可以自定义
};
// --- 可自定义区域结束 ---
// 每次代码块刷新时,都先清空旧内容,以确保修改能生效
this.innerHTML = '';
const style = document.createElement('style');
style.textContent = `
.protyle-wysiwyg {
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
background: #000 !important;
height: 100vh;
width: 100vw;
position: fixed;
top: 0;
left: 0;
z-index: 9998;
}
.rain-canvas, .text-canvas, .heart-canvas {
position: absolute;
top:0;
left:0;
width: 100%;
height: 100%;
}
.heart-text {
position: fixed;
top: 42%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
pointer-events: none;
display: none;
}
.heart-text h4 {
font-family: "STKaiti";
font-size: 40px;
color: #f584b7;
text-align: center;
white-space: nowrap;
}
.close-btn {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
color: white;
background: rgba(255,255,255,0.2);
border: 1px solid white;
border-radius: 50%;
width: 30px;
height: 30px;
text-align: center;
line-height: 28px;
cursor: pointer;
font-size: 20px;
}
`;
this.appendChild(style);
const container = document.createElement('div');
container.className = 'protyle-wysiwyg';
this.appendChild(container);
const rainCanvas = document.createElement('canvas');
rainCanvas.id = 'rain-canvas';
rainCanvas.className = 'rain-canvas';
container.appendChild(rainCanvas);
const textCanvas = document.createElement('canvas');
textCanvas.className = 'text-canvas';
container.appendChild(textCanvas);
const heartTextContainer = document.createElement('div');
heartTextContainer.className = 'heart-text';
heartTextContainer.innerHTML = `<h4>💗 ${customTexts.heart}</h4>`;
container.appendChild(heartTextContainer);
const heartCanvas = document.createElement('canvas');
heartCanvas.id = 'heart-canvas';
heartCanvas.className = 'heart-canvas';
heartCanvas.style.display = 'none';
container.appendChild(heartCanvas);
const closeButton = document.createElement('div');
closeButton.className = 'close-btn';
closeButton.innerHTML = '×';
container.appendChild(closeButton);
// 新增:ESC 退出功能
const handleEscKey = (event) => {
if (event.key === 'Escape') {
closeButton.click();
}
};
document.addEventListener('keydown', handleEscKey);
closeButton.addEventListener('click', () => {
// 移除 ESC 键的监听
document.removeEventListener('keydown', handleEscKey);
// 移除所有创建的元素和样式
const createdContainer = this.querySelector('.protyle-wysiwyg');
if (createdContainer) createdContainer.remove();
const createdStyle = this.querySelector('style');
if (createdStyle) createdStyle.remove();
const blockId = this.getAttribute('data-node-id');
const finalMessage = customTexts.finalMessage || '祝你表白成功!';
// 使用 API 更新块内容为最终消息
// 这会用一个新的段落块替换掉当前的代码块,从而彻底停止脚本
fetch('/api/block/updateBlock', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: blockId,
dataType: 'markdown',
data: finalMessage
})
}).catch(error => {
console.error('更新块内容失败:', error);
// API调用失败时的后备方案:直接写入文本
this.innerHTML = finalMessage;
});
});
// ==================== 字符雨效果 ====================
function initRainEffect() {
var canvas = rainCanvas;
var ctx = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
var texts = customTexts.rain.split('');
var fontSize = 16;
var columns = canvas.width / fontSize;
var drops = [];
for (var x = 0; x < columns; x++) {
drops[x] = 1;
}
function draw() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#f584b7';
ctx.font = fontSize + 'px arial';
for (var i = 0; i < drops.length; i++) {
var text = texts[Math.floor(Math.random() * texts.length)];
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
if (drops[i] * fontSize > canvas.height || Math.random() > 0.95) {
drops[i] = 0;
}
drops[i]++;
}
}
const rainInterval = setInterval(draw, 33);
const resizeHandler = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
columns = canvas.width / fontSize;
drops = [];
for (var x = 0; x < columns; x++) {
drops[x] = 1;
}
};
window.addEventListener('resize', resizeHandler);
closeButton.addEventListener('click', () => {
clearInterval(rainInterval);
window.removeEventListener('resize', resizeHandler);
});
}
// ==================== 心形效果 ====================
function initHeartEffect() {
var settings = {
particles: {
length: 500,
duration: 2,
velocity: 100,
effect: -0.75,
size: 30,
},
};
var Point = function(x, y) {
this.x = typeof x !== 'undefined' ? x : 0;
this.y = typeof y !== 'undefined' ? y : 0;
};
Point.prototype = {
clone: function() {
return new Point(this.x, this.y);
},
length: function(length) {
if (typeof length === 'undefined')
return Math.sqrt(this.x * this.x + this.y * this.y);
this.normalize();
this.x *= length;
this.y *= length;
return this;
},
normalize: function() {
var length = this.length();
this.x /= length;
this.y /= length;
return this;
}
};
var Particle = function() {
this.position = new Point();
this.velocity = new Point();
this.acceleration = new Point();
this.age = 0;
};
Particle.prototype = {
initialize: function(x, y, dx, dy) {
this.position.x = x;
this.position.y = y;
this.velocity.x = dx;
this.velocity.y = dy;
this.acceleration.x = dx * settings.particles.effect;
this.acceleration.y = dy * settings.particles.effect;
this.age = 0;
},
update: function(deltaTime) {
this.position.x += this.velocity.x * deltaTime;
this.position.y += this.velocity.y * deltaTime;
this.velocity.x += this.acceleration.x * deltaTime;
this.velocity.y += this.acceleration.y * deltaTime;
this.age += deltaTime;
},
draw: function(context, image) {
function ease(t) {
return (--t) * t * t + 1;
}
var size = image.width * ease(this.age / settings.particles.duration);
context.globalAlpha = 1 - this.age / settings.particles.duration;
context.drawImage(image, this.position.x - size / 2, this.position.y - size / 2, size, size);
}
};
var ParticlePool = (function(length) {
var particles = new Array(length);
for (var i = 0; i < particles.length; i++) {
particles[i] = new Particle();
}
var firstActive = 0;
var firstFree = 0;
var duration = settings.particles.duration;
return {
add: function(x, y, dx, dy) {
particles[firstFree].initialize(x, y, dx, dy);
firstFree++;
if (firstFree === particles.length) firstFree = 0;
if (firstActive === firstFree) firstActive++;
if (firstActive === particles.length) firstActive = 0;
},
update: function(deltaTime) {
var i;
if (firstActive < firstFree) {
for (i = firstActive; i < firstFree; i++) {
particles[i].update(deltaTime);
}
}
if (firstFree < firstActive) {
for (i = firstActive; i < particles.length; i++) {
particles[i].update(deltaTime);
}
for (i = 0; i < firstFree; i++) {
particles[i].update(deltaTime);
}
}
while (particles[firstActive].age >= duration && firstActive !== firstFree) {
firstActive++;
if (firstActive === particles.length) firstActive = 0;
}
},
draw: function(context, image) {
var i;
if (firstActive < firstFree) {
for (i = firstActive; i < firstFree; i++) {
particles[i].draw(context, image);
}
}
if (firstFree < firstActive) {
for (i = firstActive; i < particles.length; i++) {
particles[i].draw(context, image);
}
for (i = 0; i < firstFree; i++) {
particles[i].draw(context, image);
}
}
}
};
})(settings.particles.length);
function startHeartEffect() {
var canvas = heartCanvas;
var context = canvas.getContext('2d');
canvas.style.display = 'block';
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
heartTextContainer.style.display = 'block';
var particles = ParticlePool;
var particleRate = settings.particles.length / settings.particles.duration;
var time;
let animationFrameId;
function pointOnHeart(t) {
return new Point(
160 * Math.pow(Math.sin(t), 3),
130 * Math.cos(t) - 50 * Math.cos(2 * t) - 20 * Math.cos(3 * t) - 10 * Math.cos(4 * t) + 25
);
}
var image = (function() {
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
canvas.width = settings.particles.size;
canvas.height = settings.particles.size;
function to(t) {
var point = pointOnHeart(t);
point.x = settings.particles.size / 2 + point.x * settings.particles.size / 350;
point.y = settings.particles.size / 2 - point.y * settings.particles.size / 350;
return point;
}
context.beginPath();
var t = -Math.PI;
var point = to(t);
context.moveTo(point.x, point.y);
while (t < Math.PI) {
t += 0.01;
point = to(t);
context.lineTo(point.x, point.y);
}
context.closePath();
context.fillStyle = '#ea80b0';
context.fill();
var image = new Image();
image.src = canvas.toDataURL();
return image;
})();
function render() {
animationFrameId = requestAnimationFrame(render);
var newTime = new Date().getTime() / 1000,
deltaTime = newTime - (time || newTime);
time = newTime;
context.clearRect(0, 0, canvas.width, canvas.height);
var amount = particleRate * deltaTime;
for (var i = 0; i < amount; i++) {
var pos = pointOnHeart(Math.PI - 2 * Math.PI * Math.random());
var dir = pos.clone().length(settings.particles.velocity);
particles.add(canvas.width / 2 + pos.x, canvas.height / 2 - pos.y, dir.x, -dir.y);
}
particles.update(deltaTime);
particles.draw(context, image);
}
const resizeHandler = () => {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
};
window.addEventListener('resize', resizeHandler);
closeButton.addEventListener('click', () => {
cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', resizeHandler);
});
render();
}
return {
start: startHeartEffect
};
}
// ==================== 文字特效 ====================
function initTextEffect() {
var Drawing = {
canvas: null,
context: null,
renderFn: null,
requestFrame: window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
},
animationFrameId: null,
init: function(el) {
this.canvas = textCanvas;
this.context = this.canvas.getContext('2d');
this.adjustCanvas();
const resizeHandler = () => this.adjustCanvas();
window.addEventListener('resize', resizeHandler);
closeButton.addEventListener('click', () => {
window.removeEventListener('resize', resizeHandler);
this.stopLoop();
});
},
stopLoop: function() {
if (this.animationFrameId) {
window.cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = null;
}
},
loop: function(fn) {
this.renderFn = !this.renderFn ? fn : this.renderFn;
this.clearFrame();
this.renderFn();
this.animationFrameId = this.requestFrame.call(window, this.loop.bind(this));
},
adjustCanvas: function() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
},
clearFrame: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
getArea: function() {
return {
w: this.canvas.width,
h: this.canvas.height
};
},
drawCircle: function(p, c) {
this.context.fillStyle = c.render();
this.context.beginPath();
this.context.arc(p.x, p.y, p.z, 0, 2 * Math.PI, true);
this.context.closePath();
this.context.fill();
}
};
var Point = function(args) {
this.x = args.x;
this.y = args.y;
this.z = args.z;
this.a = args.a;
this.h = args.h;
};
var Color = function(r, g, b, a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
this.render = function() {
return 'rgba(' + this.r + ',' + this.g + ',' + this.b + ',' + this.a + ')';
};
};
var Dot = function(x, y) {
this.p = new Point({
x: x,
y: y,
z: 5,
a: 1,
h: 0
});
this.e = 0.07;
this.s = true;
this.c = new Color(255, 255, 255, this.p.a);
this.t = this.clone();
this.q = [];
};
Dot.prototype = {
clone: function() {
return new Point({
x: this.x,
y: this.y,
z: this.z,
a: this.a,
h: this.h
});
},
_draw: function() {
this.c.a = this.p.a;
Drawing.drawCircle(this.p, this.c);
},
_moveTowards: function(n) {
var details = this.distanceTo(n, true),
dx = details[0],
dy = details[1],
d = details[2],
e = this.e * d;
if (this.p.h === -1) {
this.p.x = n.x;
this.p.y = n.y;
return true;
}
if (d > 1) {
this.p.x -= ((dx / d) * e);
this.p.y -= ((dy / d) * e);
} else {
if (this.p.h > 0) {
this.p.h--;
} else {
return true;
}
}
return false;
},
_update: function() {
if (this._moveTowards(this.t)) {
var p = this.q.shift();
if (p) {
this.t.x = p.x || this.p.x;
this.t.y = p.y || this.p.y;
this.t.z = p.z || this.p.z;
this.t.a = p.a || this.p.a;
this.p.h = p.h || 0;
} else {
if (this.s) {
this.p.x -= Math.sin(Math.random() * 3.142);
this.p.y -= Math.sin(Math.random() * 3.142);
} else {
this.move(new Point({
x: this.p.x + (Math.random() * 50) - 25,
y: this.p.y + (Math.random() * 50) - 25,
}));
}
}
}
var d = this.p.a - this.t.a;
this.p.a = Math.max(0.1, this.p.a - (d * 0.05));
d = this.p.z - this.t.z;
this.p.z = Math.max(1, this.p.z - (d * 0.05));
},
distanceTo: function(n, details) {
var dx = this.p.x - n.x,
dy = this.p.y - n.y,
d = Math.sqrt(dx * dx + dy * dy);
return details ? [dx, dy, d] : d;
},
move: function(p, avoidStatic) {
if (!avoidStatic || (avoidStatic && this.distanceTo(p) > 1)) {
this.q.push(p);
}
},
render: function() {
this._update();
this._draw();
}
};
var Shape = {
dots: [],
width: 0,
height: 0,
cx: 0,
cy: 0,
compensate: function() {
var a = Drawing.getArea();
this.cx = a.w / 2 - this.width / 2;
this.cy = a.h / 2 - this.height / 2;
},
shuffleIdle: function() {
var a = Drawing.getArea();
for (var d = 0; d < this.dots.length; d++) {
if (!this.dots[d].s) {
this.dots[d].move({
x: Math.random() * a.w,
y: Math.random() * a.h
});
}
}
},
switchShape: function(n, fast) {
var size, a = Drawing.getArea();
this.width = n.w;
this.height = n.h;
this.compensate();
if (n.dots.length > this.dots.length) {
size = n.dots.length - this.dots.length;
for (var d = 1; d <= size; d++) {
this.dots.push(new Dot(a.w / 2, a.h / 2));
}
}
var d = 0,
i;
while (n.dots.length > 0) {
i = Math.floor(Math.random() * n.dots.length);
this.dots[d].e = fast ? 0.25 : (this.dots[d].s ? 0.14 : 0.11);
if (this.dots[d].s) {
this.dots[d].move(new Point({
z: Math.random() * 20 + 10,
a: Math.random(),
h: 18
}));
} else {
this.dots[d].move(new Point({
z: Math.random() * 5 + 5,
h: fast ? 18 : 30
}));
}
this.dots[d].s = true;
this.dots[d].move(new Point({
x: n.dots[i].x + this.cx,
y: n.dots[i].y + this.cy,
a: 1,
z: 5,
h: 0
}));
n.dots = n.dots.slice(0, i).concat(n.dots.slice(i + 1));
d++;
}
for (var i = d; i < this.dots.length; i++) {
if (this.dots[i].s) {
this.dots[i].move(new Point({
z: Math.random() * 20 + 10,
a: Math.random(),
h: 20
}));
this.dots[i].s = false;
this.dots[i].e = 0.04;
this.dots[i].move(new Point({
x: Math.random() * a.w,
y: Math.random() * a.h,
a: 0.3,
z: Math.random() * 4,
h: 0
}));
}
}
},
render: function() {
for (var d = 0; d < this.dots.length; d++) {
this.dots[d].render();
}
}
};
var ShapeBuilder = {
gap: 13,
shapeCanvas: document.createElement('canvas'),
shapeContext: null,
fontSize: 500,
fontFamily: 'Avenir, Helvetica Neue, Helvetica, Arial, sans-serif',
init: function() {
this.shapeContext = this.shapeCanvas.getContext('2d');
this.fit();
const resizeHandler = () => this.fit();
window.addEventListener('resize', resizeHandler);
closeButton.addEventListener('click', () => {
window.removeEventListener('resize', resizeHandler);
});
},
fit: function() {
this.shapeCanvas.width = Math.floor(window.innerWidth / this.gap) * this.gap;
this.shapeCanvas.height = Math.floor(window.innerHeight / this.gap) * this.gap;
this.shapeContext.fillStyle = 'red';
this.shapeContext.textBaseline = 'middle';
this.shapeContext.textAlign = 'center';
},
processCanvas: function() {
var pixels = this.shapeContext.getImageData(0, 0, this.shapeCanvas.width, this.shapeCanvas.height).data;
var dots = [],
x = 0,
y = 0,
fx = this.shapeCanvas.width,
fy = this.shapeCanvas.height,
w = 0,
h = 0;
for (var p = 0; p < pixels.length; p += (4 * this.gap)) {
if (pixels[p + 3] > 0) {
dots.push(new Point({
x: x,
y: y
}));
w = x > w ? x : w;
h = y > h ? y : h;
fx = x < fx ? x : fx;
fy = y < fy ? y : fy;
}
x += this.gap;
if (x >= this.shapeCanvas.width) {
x = 0;
y += this.gap;
p += this.gap * 4 * this.shapeCanvas.width;
}
}
return {
dots: dots,
w: w + fx,
h: h + fy
};
},
setFontSize: function(s) {
this.shapeContext.font = 'bold ' + s + 'px ' + this.fontFamily;
},
letter: function(l) {
var s = 0;
this.setFontSize(this.fontSize);
s = Math.min(this.fontSize,
(this.shapeCanvas.width / this.shapeContext.measureText(l).width) * 0.8 * this.fontSize,
(this.shapeCanvas.height / this.fontSize) * (isFinite(l) ? 1 : 0.45) * this.fontSize);
this.setFontSize(s);
this.shapeContext.clearRect(0, 0, this.shapeCanvas.width, this.shapeCanvas.height);
this.shapeContext.fillText(l, this.shapeCanvas.width / 2, this.shapeCanvas.height / 2);
return this.processCanvas();
}
};
let timeouts = [];
let intervals = [];
function simulate(sequence) {
var current;
var index = 0;
function next() {
if (index < sequence.length) {
current = sequence[index];
index++;
if (current === '#countdown 3') {
var count = 3;
var countdown = setInterval(function() {
if (count > 0) {
Shape.switchShape(ShapeBuilder.letter(count), true);
} else {
clearInterval(countdown);
next();
}
count--;
}, 1000);
intervals.push(countdown);
} else if (current === '#rectangle') {
const timeout = setTimeout(function() {
textCanvas.style.display = 'none';
var heartEffect = initHeartEffect();
heartEffect.start();
}, 1000);
timeouts.push(timeout);
} else {
Shape.switchShape(ShapeBuilder.letter(current));
const timeout = setTimeout(next, 2000);
timeouts.push(timeout);
}
}
}
next();
}
closeButton.addEventListener('click', () => {
timeouts.forEach(clearTimeout);
intervals.forEach(clearInterval);
});
Drawing.init('.text-canvas');
ShapeBuilder.init();
Drawing.loop(function() {
Shape.render();
});
simulate(customTexts.sequence);
}
// ==================== 初始化所有效果 ====================
initRainEffect();
initTextEffect();
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于