最近刷视频刷到一个全屏弹幕的视频,所以我自己也做了一个。
先看视频:
代码实现
源码如下
#!/usr/bin/env python3
import tkinter as tk
import random
import sys
from time import sleep
# 弹窗数量
WINDOWS_NUM = 128
# 弹窗大小
WINDOWS_SIZE = (500, 80)
# 弹窗间隔(ms)
WINDOWS_INTERVAL = 100
# 弹窗内容
MESSAGES = [
"我不是不开心,只是不知道该怎么开心",
"连崩溃都要挑时间的人,真的很懂事",
"月亮不会奔向你,但我会",
"明明什么都没发生,却觉得什么都结束了",
"其实我也想被谁坚定地选择一次",
"不想再做梦了,梦醒太难过",
"我一直在假装很快乐",
"希望你别太早看透人生,看透了就真的无趣了",
"突然就不想努力了,也不想说话",
"想被偏爱,也想被理解,但好像都太奢侈",
"所有的热情都会在失望中慢慢变冷",
"夜晚的风吹不走压抑",
"想念不一定要见面,有时候回忆就够了",
"人这一生,最怕突然想通又突然心痛",
"我笑着说没关系,其实已经碎了一地",
"我也想被一个人坚定地喜欢一次",
"那些没说出口的委屈,后来都变成了沉默",
"如果有下次,我希望我不是那个主动的人",
"没人能看见我凌晨三点的样子",
"我什么都没说,但你什么都懂——那就算了吧",
"你走之后,我连梦都变得小心翼翼",
"我假装释怀,其实一想到你还是会红了眼",
"失去你那天,我学会了笑着说没事",
"我以为我们能久一点,结果只是以为",
"你没有挽留,我也没再回头",
"后来的我,连听情歌都小心翼翼",
"你是我心口的那颗钉,拔不掉也忘不掉",
"我删了聊天记录,却删不掉回忆",
"我好像把所有的温柔都给错了人",
"你教会了我什么叫“自作多情”",
"我不怪你离开,只怪我没能让你留下",
"你是我没说出口的心动,也是我没来得及的遗憾",
"再见面时,我一定要笑得比现在更好看",
"我装作无所谓,其实连朋友圈都不敢看",
"最难过的不是分开,而是从此没资格关心",
"你走的那天,连天气都没挽留我",
"我终于学会不打扰,可你早已不在意",
"爱的时候像救赎,散的时候像坠落",
"后来我们都没再提起,但都记得",
"我没放下你,只是学会了不提你"
]
# 基于操作系统的自动配置
# mac下圆角
RADIUS = 30 if sys.platform == "darwin" else 0
# 字体
FONT = "Arial" if sys.platform == "darwin" else "Microsoft Yahei"
class FancyPopup:
def __init__(self, root, text, size=WINDOWS_SIZE, duration=4000):
self.root = root
self.text = text
self.duration = duration
self.win = tk.Toplevel(root)
self.win.overrideredirect(True)
self.win.attributes("-topmost", True)
self.win.attributes("-alpha", 0.0) # 初始透明
self.bg_color = self.random_color()
# window下圆角背景无法透明,取消圆角
self.radius = RADIUS
# 随机位置
sw, sh = self.win.winfo_screenwidth(), self.win.winfo_screenheight()
w, h = size
self.x = random.randint(-50, sw - w + 50)
self.y = random.randint(0, sh - h - 90) # -90 为mac下方程序坞的高度,win下可以删去
self.win.geometry(f"{w}x{h}+{self.x}+{self.y}")
# 创建圆角画布
self.canvas = tk.Canvas(self.win, width=w, height=h, highlightthickness=0)
self.canvas.pack(fill="both", expand=True)
self.draw_rounded_rect(0, 0, w, h, self.radius, self.bg_color)
# 添加文本, 自动计算字体大小(单行适配)
font_size = self.auto_fit_text_size(self.text, w - 60)
self.text_id = self.canvas.create_text(w / 2, h / 2, text=self.text, fill="white",
font=(FONT, font_size, "bold"), justify="center", width=w - 60, )
self.fade_in()
def auto_fit_text_size(self, text, max_width):
"""根据窗口宽度自动调整字体大小,让文字恰好一行"""
test_font_size = 32
test_font = (FONT, test_font_size, "bold")
temp_id = self.canvas.create_text(0, 0, text=text, font=test_font, anchor="nw")
bbox = self.canvas.bbox(temp_id)
text_width = bbox[2] - bbox[0]
self.canvas.delete(temp_id)
scale = max_width / text_width
new_size = max(10, int(test_font_size * scale))
return min(new_size, 32) # 限制最大字号,防止太大
def draw_rounded_rect(self, x1, y1, x2, y2, r, color):
points = [x1 + r, y1, x1 + r, y1, x2 - r, y1, x2 - r, y1, x2, y1, x2, y1 + r, x2, y1 + r, x2, y2 - r, x2,
y2 - r, x2, y2, x2 - r, y2, x2 - r, y2, x1 + r, y2, x1 + r, y2, x1, y2, x1, y2 - r, x1, y2 - r, x1,
y1 + r, x1, y1 + r, x1, y1, ]
self.canvas.create_polygon(points, smooth=True, fill=color, outline=color)
def random_color(self):
return f"#{random.randint(30, 220):02x}{random.randint(30, 220):02x}{random.randint(30, 220):02x}"
def fade_in(self, step=0):
alpha = step / 20
if alpha <= 1:
self.win.attributes("-alpha", alpha)
self.win.after(30, lambda: self.fade_in(step + 1))
else:
self.win.attributes("-alpha", 1.0)
def fade_out(self, step=0):
alpha = 1 - step / 20
if alpha > 0:
self.win.attributes("-alpha", alpha)
self.win.after(20, lambda: self.fade_out(step + 1))
else:
self.destroy()
def destroy(self):
try:
self.win.destroy()
except:
pass
class PopupController:
def __init__(self, messages, interval):
self.root = tk.Tk()
self.root.withdraw()
self.popups = []
self.messages = messages
self.interval = interval
self.spawn_num = WINDOWS_NUM
self.finished = False
def start(self):
self.spawn_next()
self.root.mainloop()
def spawn_next(self):
if len(self.popups) >= self.spawn_num:
# 所有弹窗出来后开始消失
self.finished = True
self.popups = self.popups[::-1]
self.root.after(500, self.start_fading, 1)
return
msg = self.messages[random.randint(0, len(self.messages) - 1)]
popup = FancyPopup(self.root, msg)
print(f"位置:{popup.x:>4}, {popup.y:<4} , 弹窗: {msg}")
self.popups.append(popup)
self.root.after(self.interval, self.spawn_next)
def start_fading(self, count):
if not self.popups:
self.root.after(1000, lambda: sleep(1))
self.root.quit()
return
remove_n = min(count, len(self.popups))
to_remove = self.popups[:remove_n]
for p in to_remove:
p.fade_out()
self.popups = self.popups[remove_n:]
next_count = min(15, count + random.randint(0, 1))
self.root.after(150, self.start_fading, next_count)
if __name__ == "__main__":
try:
app = PopupController(MESSAGES.copy(), WINDOWS_INTERVAL)
app.start()
except KeyboardInterrupt:
sys.exit(0)预览图

整体脚本还是比较简单,我对其进行了封装,方便调用和二次开发拓展。核心参数就可以在代码的头部继续修改。
评论