情人节是一个表达爱意的好机会,作为一名程序猿,平时经常会被吐槽木讷不懂浪漫,2.14号即将到来,让我们来一次绝地反击吧,用我们的技术展现浓浓的的爱意,利用Python和HTML制作一个独特的情人节告白爱心,相信一定能打动你的那个她。
文章目录
一、Python实现(电视剧《点燃我,温暖你》版本)跳动的爱心
一、Python实现(电视剧《点燃我,温暖你》版本)跳动的爱心
1.效果预览:
2.代码分步介绍:
1)模块导入:
主要用到tkinter模块,Python的GUI工具包接口。
import random
from math import sin, cos, pi, log
from tkinter import *
2)形状颜色设置:
长宽、颜色都可以自己修改,颜色的具体参数可以参考:tkinter中颜色的代码
#FFB6C1 LightPink 浅粉红 | #FFC0CB Pink 粉红 |
#DC143C Crimson 深红/猩红 | #FF69B4 HotPink 热情的粉红 |
#C71585 MediumVioletRed 中紫罗兰红 | #DA70D6 Orchid 暗紫色/兰花紫 |
#DDA0DD Plum 洋李色/李子紫 | #EE82EE Violet 紫罗兰 |
#FF00FF Magenta 洋红/玫瑰红 | #FF00FF Fuchsia 紫红/灯笼海棠 |
#8B008B DarkMagenta 深洋红 | #800080 Purple 紫色 |
#BA55D3 MediumOrchid 中兰花紫 | #9400D3 DarkViolet 暗紫罗兰 |
#4B0082 Indigo 靛青/紫兰色 | #8A2BE2 BlueViolet 蓝紫罗兰 |
#9370DB MediumPurple 中紫色 | #7B68EE MediumSlateBlue 中暗蓝色/中板岩蓝 |
#483D8B DarkSlateBlue 暗灰蓝色/暗板岩蓝 | #E6E6FA Lavender 淡紫色/熏衣草淡紫 |
#000080 Navy 海军蓝 | #4169E1 RoyalBlue 皇家蓝/宝蓝 |
#6495ED CornflowerBlue 矢车菊蓝 | #B0C4DE LightSteelBlue 亮钢蓝 |
#778899 LightSlateGray 亮蓝灰/亮石板灰 | #1E90FF DodgerBlue 闪兰色/道奇蓝 |
#F0F8FF AliceBlue 爱丽丝蓝 | #4682B4 SteelBlue 钢蓝/铁青 |
#87CEFA LightSkyBlue 亮天蓝色 | #87CEEB SkyBlue 天蓝色 |
#00BFFF DeepSkyBlue 深天蓝 | #B0E0E6 PowderBlue 粉蓝色/火药青 |
#5F9EA0 CadetBlue 军兰色/军服蓝 | #F0FFFF Azure 蔚蓝色 |
#E0FFFF LightCyan 淡青色 | #AFEEEE PaleTurquoise 弱绿宝石 |
#00FFFF Cyan 青色 | #00FFFF Aqua 浅绿色/水色 |
#00CED1 DarkTurquoise 暗绿宝石 | #48D1CC MediumTurquoise 中绿宝石 |
#40E0D0 Turquoise 绿宝石 | #7FFFD4 Aquamarine 宝石碧绿 |
#66CDAA MediumAquamarine 中宝石碧绿 | #20B2AA LightSeaGreen 浅海洋绿 |
#00FA9A MediumSpringGreen 中春绿色 | #F5FFFA MintCream 薄荷奶油 |
#00FF7F SpringGreen 春绿色 | #3CB371 MediumSeaGreen 中海洋绿 |
#98FB98 PaleGreen 弱绿色 | #8FBC8F DarkSeaGreen 暗海洋绿 |
#00FF00 Lime 闪光绿 | #228B22 ForestGreen 森林绿 |
#006400 DarkGreen 暗绿色 | #7FFF00 Chartreuse 黄绿色/查特酒绿 |
#7CFC00 LawnGreen 草绿色/草坪绿 | #ADFF2F GreenYellow 绿黄色 |
#6B8E23 OliveDrab 橄榄褐色 | #808000 Olive 橄榄 |
#BDB76B DarkKhaki 暗黄褐色/深卡叽布 | #FFFACD LemonChiffon 柠檬绸 |
#FFD700 Gold 金色 | #DAA520 Goldenrod 金菊黄 |
#FFE4B5 Moccasin 鹿皮色/鹿皮靴 | #FFA500 Orange 橙色 |
#FFEFD5 PapayaWhip 番木色/番木瓜 | #FF8C00 DarkOrange 深橙色 |
#FFDAB9 PeachPuff 桃肉色 | #F4A460 SandyBrown 沙棕色 |
#D2691E Chocolate 巧克力色 | #8B4513 SaddleBrown 重褐色/马鞍棕色 |
#FFF5EE Seashell 海贝壳 | #FF7F50 Coral 珊瑚 |
#FF4500 OrangeRed 橙红色 | #E9967A DarkSalmon 深鲜肉/鲑鱼色 |
#FF6347 Tomato 番茄红 | #FFE4E1 MistyRose 浅玫瑰色/薄雾玫瑰 |
#8B0000 DarkRed 深红色 | #DCDCDC Gainsboro 淡灰色 |
#D3D3D3 LightGrey 浅灰色 | #C0C0C0 Silver 银灰色 |
CANVAS_WIDTH = 1280 # 宽
CANVAS_HEIGHT = 840 # 高
CANVAS_CENTER_X = CANVAS_WIDTH / 2
CANVAS_CENTER_Y = CANVAS_HEIGHT / 2
IMAGE_ENLARGE = 12 # 放大比例
HEART_COLOR = "#FFB6C1"
3)爱心类:
定义一个爱心的类,里面包含实现爱心的轮廓、缩放,扩散,光环等函数。
class Heart:
"""
爱心类
"""
def __init__(self, generate_frame=20):
self._points = set() # 原始爱心坐标集合
self._edge_diffusion_points = set() # 边缘扩散效果点坐标集合
self._center_diffusion_points = set() # 中心扩散效果点坐标集合
self.all_points = {} # 每帧动态点坐标
self.build(2000)
self.random_halo = 1000
self.generate_frame = generate_frame
for frame in range(generate_frame):
self.calc(frame)
def build(self, number):
# 爱心
for _ in range(number):
t = random.uniform(0, 2 * pi) # 随机不到的地方造成爱心有缺口
x, y = heart_function(t)
self._points.add((x, y))
# 爱心内扩散
for _x, _y in list(self._points):
for _ in range(3):
x, y = scatter_inside(_x, _y, 0.05)
self._edge_diffusion_points.add((x, y))
# 爱心内再次扩散
point_list = list(self._points)
for _ in range(4000):
x, y = random.choice(point_list)
x, y = scatter_inside(x, y, 0.17)
self._center_diffusion_points.add((x, y))
@staticmethod
def calc_position(x, y, ratio):
# 调整缩放比例
force = 1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.520) # 魔法参数
dx = ratio * force * (x - CANVAS_CENTER_X) + random.randint(-1, 1)
dy = ratio * force * (y - CANVAS_CENTER_Y) + random.randint(-1, 1)
return x - dx, y - dy
def calc(self, generate_frame):
ratio = 10 * curve(generate_frame / 10 * pi) # 圆滑的周期的缩放比例
halo_radius = int(4 + 6 * (1 + curve(generate_frame / 10 * pi)))
halo_number = int(3000 + 4000 * abs(curve(generate_frame / 10 * pi) ** 2))
all_points = []
# 光环
heart_halo_point = set() # 光环的点坐标集合
for _ in range(halo_number):
t = random.uniform(0, 2 * pi) # 随机不到的地方造成爱心有缺口
x, y = heart_function(t, shrink_ratio=11.6) # 魔法参数
x, y = shrink(x, y, halo_radius)
if (x, y) not in heart_halo_point:
# 处理新的点
heart_halo_point.add((x, y))
x += random.randint(-14, 14)
y += random.randint(-14, 14)
size = random.choice((1, 2, 2))
all_points.append((x, y, size))
# 轮廓
for x, y in self._points:
x, y = self.calc_position(x, y, ratio)
size = random.randint(1, 3)
all_points.append((x, y, size))
# 内容
for x, y in self._edge_diffusion_points:
x, y = self.calc_position(x, y, ratio)
size = random.randint(1, 2)
all_points.append((x, y, size))
for x, y in self._center_diffusion_points:
x, y = self.calc_position(x, y, ratio)
size = random.randint(1, 2)
all_points.append((x, y, size))
self.all_points[generate_frame] = all_points
def render(self, render_canvas, render_frame):
for x, y, size in self.all_points[render_frame % self.generate_frame]:
render_canvas.create_rectangle(x, y, x + size, y + size, width=0, fill=HEART_COLOR)
4)生成爱心函数:
生成爱心的函数主要用来计算粒子的坐标位置
def heart_function(t, shrink_ratio: float = IMAGE_ENLARGE):
"""
“爱心函数生成器”
:param shrink_ratio: 放大比例
:param t: 参数
:return: 坐标
"""
# 基础函数
x = 16 * (sin(t) ** 3)
y = -(13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t))
# 放大
x *= shrink_ratio
y *= shrink_ratio
# 移到画布中央
x += CANVAS_CENTER_X
y += CANVAS_CENTER_Y
return int(x), int(y)
def scatter_inside(x, y, beta=0.15):
"""
随机内部扩散
:param x: 原x
:param y: 原y
:param beta: 强度
:return: 新坐标
"""
ratio_x = - beta * log(random.random())
ratio_y = - beta * log(random.random())
dx = ratio_x * (x - CANVAS_CENTER_X)
dy = ratio_y * (y - CANVAS_CENTER_Y)
return x - dx, y - dy
def shrink(x, y, ratio):
"""
抖动
:param x: 原x
:param y: 原y
:param ratio: 比例
:return: 新坐标
"""
force = -1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.6)
dx = ratio * force * (x - CANVAS_CENTER_X)
dy = ratio * force * (y - CANVAS_CENTER_Y)
return x - dx, y - dy
def curve(p):
"""
自定义曲线函数,调整跳动周期
:param p: 参数
:return: 正弦
"""
return 2 * (2 * sin(4 * p)) / (2 * pi)
5)主函数入口:
if __name__ == '__main__':
root = Tk() # 一个Tk
canvas = Canvas(root, bg='black', height=CANVAS_HEIGHT, width=CANVAS_WIDTH)
canvas.pack()
heart = Heart() # 心
draw(root, canvas, heart)
root.mainloop()
3.完整代码:复制后可直接运行 !!!
import random
from math import sin, cos, pi, log
from tkinter import *
CANVAS_WIDTH = 1280 # 宽
CANVAS_HEIGHT = 840 # 高
CANVAS_CENTER_X = CANVAS_WIDTH / 2
CANVAS_CENTER_Y = CANVAS_HEIGHT / 2
IMAGE_ENLARGE = 12 # 放大比例
HEART_COLOR = "#FFB6C1"
def heart_function(t, shrink_ratio: float = IMAGE_ENLARGE):
"""
“爱心函数生成器”
:param shrink_ratio: 放大比例
:param t: 参数
:return: 坐标
"""
# 基础函数
x = 16 * (sin(t) ** 3)
y = -(13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t))
# 放大
x *= shrink_ratio
y *= shrink_ratio
# 移到画布中央
x += CANVAS_CENTER_X
y += CANVAS_CENTER_Y
return int(x), int(y)
def scatter_inside(x, y, beta=0.15):
"""
随机内部扩散
:param x: 原x
:param y: 原y
:param beta: 强度
:return: 新坐标
"""
ratio_x = - beta * log(random.random())
ratio_y = - beta * log(random.random())
dx = ratio_x * (x - CANVAS_CENTER_X)
dy = ratio_y * (y - CANVAS_CENTER_Y)
return x - dx, y - dy
def shrink(x, y, ratio):
"""
抖动
:param x: 原x
:param y: 原y
:param ratio: 比例
:return: 新坐标
"""
force = -1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.6)
dx = ratio * force * (x - CANVAS_CENTER_X)
dy = ratio * force * (y - CANVAS_CENTER_Y)
return x - dx, y - dy
def curve(p):
"""
自定义曲线函数,调整跳动周期
:param p: 参数
:return: 正弦
"""
return 2 * (2 * sin(4 * p)) / (2 * pi)
class Heart:
"""
爱心类
"""
def __init__(self, generate_frame=20):
self._points = set() # 原始爱心坐标集合
self._edge_diffusion_points = set() # 边缘扩散效果点坐标集合
self._center_diffusion_points = set() # 中心扩散效果点坐标集合
self.all_points = {} # 每帧动态点坐标
self.build(2000)
self.random_halo = 1000
self.generate_frame = generate_frame
for frame in range(generate_frame):
self.calc(frame)
def build(self, number):
# 爱心
for _ in range(number):
t = random.uniform(0, 2 * pi) # 随机不到的地方造成爱心有缺口
x, y = heart_function(t)
self._points.add((x, y))
# 爱心内扩散
for _x, _y in list(self._points):
for _ in range(3):
x, y = scatter_inside(_x, _y, 0.05)
self._edge_diffusion_points.add((x, y))
# 爱心内再次扩散
point_list = list(self._points)
for _ in range(4000):
x, y = random.choice(point_list)
x, y = scatter_inside(x, y, 0.17)
self._center_diffusion_points.add((x, y))
@staticmethod
def calc_position(x, y, ratio):
# 调整缩放比例
force = 1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.520) # 魔法参数
dx = ratio * force * (x - CANVAS_CENTER_X) + random.randint(-1, 1)
dy = ratio * force * (y - CANVAS_CENTER_Y) + random.randint(-1, 1)
return x - dx, y - dy
def calc(self, generate_frame):
ratio = 10 * curve(generate_frame / 10 * pi) # 圆滑的周期的缩放比例
halo_radius = int(4 + 6 * (1 + curve(generate_frame / 10 * pi)))
halo_number = int(3000 + 4000 * abs(curve(generate_frame / 10 * pi) ** 2))
all_points = []
# 光环
heart_halo_point = set() # 光环的点坐标集合
for _ in range(halo_number):
t = random.uniform(0, 2 * pi) # 随机不到的地方造成爱心有缺口
x, y = heart_function(t, shrink_ratio=11.6) # 魔法参数
x, y = shrink(x, y, halo_radius)
if (x, y) not in heart_halo_point:
# 处理新的点
heart_halo_point.add((x, y))
x += random.randint(-14, 14)
y += random.randint(-14, 14)
size = random.choice((1, 2, 2))
all_points.append((x, y, size))
# 轮廓
for x, y in self._points:
x, y = self.calc_position(x, y, ratio)
size = random.randint(1, 3)
all_points.append((x, y, size))
# 内容
for x, y in self._edge_diffusion_points:
x, y = self.calc_position(x, y, ratio)
size = random.randint(1, 2)
all_points.append((x, y, size))
for x, y in self._center_diffusion_points:
x, y = self.calc_position(x, y, ratio)
size = random.randint(1, 2)
all_points.append((x, y, size))
self.all_points[generate_frame] = all_points
def render(self, render_canvas, render_frame):
for x, y, size in self.all_points[render_frame % self.generate_frame]:
render_canvas.create_rectangle(x, y, x + size, y + size, width=0, fill=HEART_COLOR)
def draw(main: Tk, render_canvas: Canvas, render_heart: Heart, render_frame=0):
render_canvas.delete('all')
render_heart.render(render_canvas, render_frame)
main.after(160, draw, main, render_canvas, render_heart, render_frame + 1)
if __name__ == '__main__':
root = Tk() # 一个Tk
canvas = Canvas(root, bg='black', height=CANVAS_HEIGHT, width=CANVAS_WIDTH)
canvas.pack()
heart = Heart() # 心
draw(root, canvas, heart)
root.mainloop()
二、用HTML实现扩散的爱心
可以直接保存成HTML格式,发送给心爱的她,当她点开网址后一定能感受到惊喜,实测有效!!
1.效果预览:
2.个性化:
1)可以在在下图body标签下<p> </p>中插入你想对心仪的她说的话!
2)同时可以调整下列参数实现爱心展现形式的个性化
3.完整代码:复制后可直接保存成HTML格式 !!!
<html>
<head>
<meta charset="utf-8">
<title>loveHeart</title>
<link rel="shortcut icon" href="http://zhouql.vip/images/心.png" type="image/x-icon">
<style>
html,
body {
height: 100%;
padding: 0;
margin: 0;
background: #000;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
}
p{
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
color: pink;
animation: k 1.5s ease-in-out infinite;
}
@keyframes k{
100%{
font-size: 24px;
opacity: 0;
}
}
</style>
</head>
<body>
<p></p>
<canvas id="pinkboard"></canvas>
<script>
var settings = {
particles: {
length: 600, // 爱心的大小
duration: 2.5, // 爱心扩散速度,越小速度越快
velocity: 100, // 爱心扩散速度,越小速度越慢
effect: -0.8, // 爱心收缩效果,比如:1扩散,-2收缩
size: 28, // 爱心数量
},
};
(function () { var b = 0; var c = ["ms", "moz", "webkit", "o"]; for (var a = 0; a < c.length && !window.requestAnimationFrame; ++a) { window.requestAnimationFrame = window[c[a] + "RequestAnimationFrame"]; window.cancelAnimationFrame = window[c[a] + "CancelAnimationFrame"] || window[c[a] + "CancelRequestAnimationFrame"] } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function (h, e) { var d = new Date().getTime(); var f = Math.max(0, 16 - (d - b)); var g = window.setTimeout(function () { h(d + f) }, f); b = d + f; return g } } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function (d) { clearTimeout(d) } } }());
var Point = (function () {
function Point(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);
};
Point.prototype.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;
};
Point.prototype.normalize = function () {
var length = this.length();
this.x /= length;
this.y /= length;
return this;
};
return Point;
})();
var Particle = (function () {
function Particle() {
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;
};
Particle.prototype.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;
};
Particle.prototype.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);
};
return Particle;
})();
var ParticlePool = (function () {
var particles,
firstActive = 0,
firstFree = 0,
duration = settings.particles.duration;
function ParticlePool(length) {
// create and populate particle pool
particles = new Array(length);
for (var i = 0; i < particles.length; i++)
particles[i] = new Particle();
}
ParticlePool.prototype.add = function (x, y, dx, dy) {
particles[firstFree].initialize(x, y, dx, dy);
// handle circular queue
firstFree++;
if (firstFree == particles.length) firstFree = 0;
if (firstActive == firstFree) firstActive++;
if (firstActive == particles.length) firstActive = 0;
};
ParticlePool.prototype.update = function (deltaTime) {
var i;
// update active particles
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);
}
// remove inactive particles
while (particles[firstActive].age >= duration && firstActive != firstFree) {
firstActive++;
if (firstActive == particles.length) firstActive = 0;
}
};
ParticlePool.prototype.draw = function (context, image) {
// draw active particles
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);
}
};
return ParticlePool;
})();
(function (canvas) {
var context = canvas.getContext('2d'),
particles = new ParticlePool(settings.particles.length),
particleRate = settings.particles.length / settings.particles.duration, // particles/sec
time;
// get point on heart with -PI <= t <= PI
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
);
}
// creating the particle image using a dummy canvas
var image = (function () {
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
canvas.width = settings.particles.size;
canvas.height = settings.particles.size;
// helper function to create the path
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;
}
// create the path
context.beginPath();
var t = -Math.PI;
var point = to(t);
context.moveTo(point.x, point.y);
while (t < Math.PI) {
t += 0.01; // baby steps!
point = to(t);
context.lineTo(point.x, point.y);
}
context.closePath();
// create the fill
context.fillStyle = '#ea80b0';
context.fill();
// create the image
var image = new Image();
image.src = canvas.toDataURL();
return image;
})();
// render that thing!
function render() {
// next animation frame
requestAnimationFrame(render);
// update time
var newTime = new Date().getTime() / 1000,
deltaTime = newTime - (time || newTime);
time = newTime;
// clear canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// create new particles
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);
}
// update and draw particles
particles.update(deltaTime);
particles.draw(context, image);
}
// handle (re-)sizing of the canvas
function onResize() {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
}
window.onresize = onResize;
// delay rendering bootstrap
setTimeout(function () {
onResize();
render();
}, 10);
})(document.getElementById('pinkboard'));
</script>
</body>
</html>
三、结语:记得点赞关注加收藏哦!!!
两个爱心版本实测都可以完美运行,完整代码如上,免费共享,给大家借鉴参考!有需要的伙伴们可以自行下载研究,共同努力共同学习!记得点赞+关注+收藏哦!