Orz,前段时间有个 zz 需求,就是要做一个类似网页上很多个多点连线、鼠标移动跟随的那种炫酷特效,然后花了点时间在网上找了 js 做的,刚开始打算是嵌入 QWebView
来显示网页,后来研究了下 js 的算法代码,遂改用 QWidget
的 paintEvent
直接绘制。
# 大概思路
- 先根据窗口大小随机创建一些点
- 遍历这些点并找到与之相关联的点
- 在动画过程中绘制圆点和画两点之间的连线
- 属性动画
QPropertyAnimation
改变颜色的透明度
# 题外
- 这里没有仔细去研究 js 里的算法优化,在浏览器里嗖嗖的就生成了,在 py 里好慢…
- 尽量在 py 里优化了循环操作,也简单的做了个 cython 加速也才提高了 1s ? 1 倍?..
- 不要只是为了好看用这玩意儿,和网页的效果一样,占 CPU !!! 没有任何意义
- 如果有更好的优化算法请告知,3Q
- pyd 是 python3.4 生成的,删掉 pyd 也能运行
# 代码
https://github.com/PyQt5/PyQt/blob/master/QPropertyAnimation/RlatticeEffect.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on 2018年11月22日
@author: Irony
@site: https://pyqt5.com, https://github.com/892768447
@email: 892768447@qq.com
@file:
@description:
"""
from random import random
from time import time
from PyQt5.QtCore import QPropertyAnimation, QObject, pyqtProperty, QEasingCurve,\
Qt, QRectF, pyqtSignal
from PyQt5.QtGui import QColor, QPainterPath, QPainter
from PyQt5.QtWidgets import QWidget
__Author__ = """By: Irony
QQ: 892768447
Email: 892768447@qq.com"""
__Copyright__ = 'Copyright (c) 2018 Irony'
__Version__ = 1.0
try:
import pointtool # @UnusedImport @UnresolvedImport
getDistance = pointtool.getDistance
findClose = pointtool.findClose
except:
import math
def getDistance(p1, p2):
return math.pow(p1.x - p2.x, 2) + math.pow(p1.y - p2.y, 2)
def findClose(points):
plen = len(points)
for i in range(plen):
closest = [None, None, None, None, None]
p1 = points[i]
for j in range(plen):
p2 = points[j]
dte1 = getDistance(p1, p2)
if p1 != p2:
placed = False
for k in range(5):
if not placed:
if not closest[k]:
closest[k] = p2
placed = True
for k in range(5):
if not placed:
if dte1 < getDistance(p1, closest[k]):
closest[k] = p2
placed = True
p1.closest = closest
class Target:
def __init__(self, x, y):
self.x = x
self.y = y
class Point(QObject):
valueChanged = pyqtSignal()
def __init__(self, x, ox, y, oy, *args, **kwargs):
super(Point, self).__init__(*args, **kwargs)
self.__x = x
self._x = x
self.originX = ox
self._y = y
self.__y = y
self.originY = oy
# 5个闭合点
self.closest = [0, 0, 0, 0, 0]
# 圆半径
self.radius = 2 + random() * 2
# 连线颜色
self.lineColor = QColor(156, 217, 249)
# 圆颜色
self.circleColor = QColor(156, 217, 249)
def initAnimation(self):
# 属性动画
if not hasattr(self, 'xanimation'):
self.xanimation = QPropertyAnimation(
self, b'x', self, valueChanged=self.valueChanged.emit,
easingCurve=QEasingCurve.InOutSine)
self.yanimation = QPropertyAnimation(
self, b'y', self, valueChanged=self.valueChanged.emit,
easingCurve=QEasingCurve.InOutSine,
finished=self.updateAnimation)
self.updateAnimation()
def updateAnimation(self):
self.xanimation.stop()
self.yanimation.stop()
duration = (1 + random()) * 1000
self.xanimation.setDuration(duration)
self.yanimation.setDuration(duration)
self.xanimation.setStartValue(self.__x)
self.xanimation.setEndValue(self.originX - 50 + random() * 100)
self.yanimation.setStartValue(self.__y)
self.yanimation.setEndValue(self.originY - 50 + random() * 100)
self.xanimation.start()
self.yanimation.start()
@pyqtProperty(float)
def x(self):
return self._x
@x.setter
def x(self, x):
self._x = x
@pyqtProperty(float)
def y(self):
return self._y
@y.setter
def y(self, y):
self._y = y
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.setMouseTracking(True)
self.resize(800, 600)
self.points = []
self.target = Target(self.width() / 2, self.height() / 2)
self.initPoints()
def paintEvent(self, event):
super(Window, self).paintEvent(event)
painter = QPainter()
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.fillRect(self.rect(), Qt.black)
self.animate(painter)
painter.end()
def mouseMoveEvent(self, event):
super(Window, self).mouseMoveEvent(event)
# 鼠标移动时更新xy坐标
self.target.x = event.x()
self.target.y = event.y()
self.update()
def initPoints(self):
t = time()
self.points.clear()
# 创建点
stepX = self.width() / 20
stepY = self.height() / 20
for x in range(0, self.width(), int(stepX)):
for y in range(0, self.height(), int(stepY)):
ox = x + random() * stepX
oy = y + random() * stepY
point = Point(ox, ox, oy, oy)
point.valueChanged.connect(self.update)
self.points.append(point)
print(time() - t)
t = time()
# 每个点寻找5个闭合点
findClose(self.points)
print(time() - t)
def animate(self, painter):
for p in self.points:
# 检测点的范围
value = abs(getDistance(self.target, p))
if value < 4000:
# 其实就是修改颜色透明度
p.lineColor.setAlphaF(0.3)
p.circleColor.setAlphaF(0.6)
elif value < 20000:
p.lineColor.setAlphaF(0.1)
p.circleColor.setAlphaF(0.3)
elif value < 40000:
p.lineColor.setAlphaF(0.02)
p.circleColor.setAlphaF(0.1)
else:
p.lineColor.setAlphaF(0)
p.circleColor.setAlphaF(0)
# 画线条
if p.lineColor.alpha():
for pc in p.closest:
if not pc:
continue
path = QPainterPath()
path.moveTo(p.x, p.y)
path.lineTo(pc.x, pc.y)
painter.save()
painter.setPen(p.lineColor)
painter.drawPath(path)
painter.restore()
# 画圆
painter.save()
painter.setPen(Qt.NoPen)
painter.setBrush(p.circleColor)
painter.drawRoundedRect(QRectF(
p.x - p.radius, p.y - p.radius, 2 * p.radius, 2 * p.radius), p.radius, p.radius)
painter.restore()
# 开启动画
p.initAnimation()
if __name__ == '__main__':
import sys
import cgitb
sys.excepthook = cgitb.enable(1, None, 5, '')
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
# 效果图
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PyQt!