背景连线动画
This commit is contained in:
parent
4207ec86eb
commit
8135d21160
6 changed files with 482 additions and 1 deletions
|
@ -1,5 +1,6 @@
|
|||
eclipse.preferences.version=1
|
||||
encoding//Demo/AutoRestart.py=utf-8
|
||||
encoding//Demo/CircleLine.py=utf-8
|
||||
encoding//Demo/EmbedWindow.py=utf-8
|
||||
encoding//Demo/FacePoints.py=utf-8
|
||||
encoding//Demo/FollowWindow.py=utf-8
|
||||
|
@ -20,6 +21,7 @@ encoding//QChart/LineChart.py=utf-8
|
|||
encoding//QFont/AwesomeFont.py=utf-8
|
||||
encoding//QFont/Lib/FontAwesome.py=utf-8
|
||||
encoding//QGraphicsDropShadowEffect/ShadowEffect.py=utf-8
|
||||
encoding//QGraphicsView/WorldMap.py=utf-8
|
||||
encoding//QListView/CustomWidgetSortItem.py=utf-8
|
||||
encoding//QListView/SortItemByRole.py=utf-8
|
||||
encoding//QMessageBox/CustomColorIcon.py=utf-8
|
||||
|
|
262
Demo/CircleLine.py
Normal file
262
Demo/CircleLine.py
Normal file
|
@ -0,0 +1,262 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Created on 2019年3月19日
|
||||
@author: Irony
|
||||
@site: https://pyqt5.com https://github.com/892768447
|
||||
@email: 892768447@qq.com
|
||||
@file: CircleLine
|
||||
@description:
|
||||
"""
|
||||
|
||||
from math import floor, pi, cos, sin
|
||||
from random import random, randint
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtCore import QTimer, Qt
|
||||
from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
||||
|
||||
__Author__ = 'Irony'
|
||||
__Copyright__ = 'Copyright (c) 2019'
|
||||
|
||||
# 最小和最大半径、半径阈值和填充圆的百分比
|
||||
radMin = 10
|
||||
radMax = 80
|
||||
filledCircle = 30 # 填充圆的百分比
|
||||
concentricCircle = 60 # 同心圆百分比
|
||||
radThreshold = 25 # IFF special, over this radius concentric, otherwise filled
|
||||
# 最小和最大移动速度
|
||||
speedMin = 0.3
|
||||
speedMax = 0.6
|
||||
# 每个圆和模糊效果的最大透明度
|
||||
maxOpacity = 0.6
|
||||
|
||||
colors = [
|
||||
QColor(52, 168, 83),
|
||||
QColor(117, 95, 147),
|
||||
QColor(199, 108, 23),
|
||||
QColor(194, 62, 55),
|
||||
QColor(0, 172, 212),
|
||||
QColor(120, 120, 120)
|
||||
]
|
||||
circleBorder = 10
|
||||
backgroundLine = colors[0]
|
||||
backgroundColor = QColor(38, 43, 46)
|
||||
backgroundMlt = 0.85
|
||||
|
||||
lineBorder = 2.5
|
||||
|
||||
# 最重要的是:包含它们的整个圆和数组的数目
|
||||
maxCircles = 8
|
||||
points = []
|
||||
|
||||
# 实验变量
|
||||
circleExp = 1
|
||||
circleExpMax = 1.003
|
||||
circleExpMin = 0.997
|
||||
circleExpSp = 0.00004
|
||||
circlePulse = False
|
||||
|
||||
# 生成随机整数 a<=x<=b
|
||||
|
||||
|
||||
def randint(a, b):
|
||||
return floor(random() * (b - a + 1) + a)
|
||||
|
||||
# 生成随机小数
|
||||
|
||||
|
||||
def randRange(a, b):
|
||||
return random() * (b - a) + a
|
||||
|
||||
# 生成接近a的随机小数
|
||||
|
||||
|
||||
def hyperRange(a, b):
|
||||
return random() * random() * random() * (b - a) + a
|
||||
|
||||
|
||||
class Circle:
|
||||
|
||||
def __init__(self, background, width, height):
|
||||
self.background = background
|
||||
self.x = randRange(-width / 2, width / 2)
|
||||
self.y = randRange(-height / 2, height / 2)
|
||||
self.radius = hyperRange(radMin, radMax)
|
||||
self.filled = (False if randint(
|
||||
0, 100) > concentricCircle else 'full') if self.radius < radThreshold else (
|
||||
False if randint(0, 100) > concentricCircle else 'concentric')
|
||||
self.color = colors[randint(0, len(colors) - 1)]
|
||||
self.borderColor = colors[randint(0, len(colors) - 1)]
|
||||
self.opacity = 0.05
|
||||
self.speed = randRange(speedMin, speedMax) # * (radMin / self.radius)
|
||||
self.speedAngle = random() * 2 * pi
|
||||
self.speedx = cos(self.speedAngle) * self.speed
|
||||
self.speedy = sin(self.speedAngle) * self.speed
|
||||
spacex = abs((self.x - (-1 if self.speedx < 0 else 1) *
|
||||
(width / 2 + self.radius)) / self.speedx)
|
||||
spacey = abs((self.y - (-1 if self.speedy < 0 else 1) *
|
||||
(height / 2 + self.radius)) / self.speedy)
|
||||
self.ttl = min(spacex, spacey)
|
||||
|
||||
|
||||
class CircleLineWindow(QWidget):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CircleLineWindow, self).__init__(*args, **kwargs)
|
||||
geometry = QApplication.instance().desktop().availableGeometry()
|
||||
self.screenWidth = geometry.width()
|
||||
self.screenHeight = geometry.height()
|
||||
self._canDraw = True
|
||||
self._firstDraw = True
|
||||
self._timer = QTimer(self, timeout=self.update)
|
||||
|
||||
def init(self):
|
||||
points.clear()
|
||||
# 链接的最小距离
|
||||
self.linkDist = min(self.screenWidth, self.screenHeight) / 2.4
|
||||
# 初始化点
|
||||
for _ in range(maxCircles * 3):
|
||||
points.append(Circle('', self.screenWidth, self.screenHeight))
|
||||
self.update()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super(CircleLineWindow, self).resizeEvent(event)
|
||||
self.init()
|
||||
|
||||
def showEvent(self, event):
|
||||
super(CircleLineWindow, self).showEvent(event)
|
||||
self._canDraw = True
|
||||
|
||||
def hideEvent(self, event):
|
||||
super(CircleLineWindow, self).hideEvent(event)
|
||||
# 窗口最小化要停止绘制, 减少cpu占用
|
||||
self._canDraw = False
|
||||
|
||||
def paintEvent(self, event):
|
||||
super(CircleLineWindow, self).paintEvent(event)
|
||||
if not self._canDraw:
|
||||
return
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
painter.setRenderHint(QPainter.SmoothPixmapTransform)
|
||||
painter.save()
|
||||
painter.fillRect(self.rect(), backgroundColor)
|
||||
painter.restore()
|
||||
self.draw(painter)
|
||||
|
||||
def draw(self, painter):
|
||||
if circlePulse:
|
||||
if circleExp < circleExpMin or circleExp > circleExpMax:
|
||||
circleExpSp *= -1
|
||||
circleExp += circleExpSp
|
||||
|
||||
painter.translate(self.screenWidth / 2, self.screenHeight / 2)
|
||||
|
||||
if self._firstDraw:
|
||||
t = time()
|
||||
self.renderPoints(painter, points)
|
||||
if self._firstDraw:
|
||||
self._firstDraw = False
|
||||
# 此处有个比例关系用于设置timer的时间,如果初始窗口很小,没有比例会导致动画很快
|
||||
t = (time() - t) * 1000
|
||||
# 比例最大不能超过1920/800
|
||||
t = int(min(2.4, self.screenHeight / self.height()) * t) - 1
|
||||
print('start timer(%d msec)' % t)
|
||||
# 开启定时器
|
||||
self._timer.start(t)
|
||||
|
||||
def drawCircle(self, painter, circle):
|
||||
# circle.radius *= circleExp
|
||||
if circle.background:
|
||||
circle.radius *= circleExp
|
||||
else:
|
||||
circle.radius /= circleExp
|
||||
radius = circle.radius
|
||||
|
||||
r = radius * circleExp
|
||||
# 边框颜色设置透明度
|
||||
c = QColor(circle.borderColor)
|
||||
c.setAlphaF(circle.opacity)
|
||||
|
||||
painter.save()
|
||||
if circle.filled == 'full':
|
||||
# 设置背景刷
|
||||
painter.setBrush(c)
|
||||
painter.setPen(Qt.NoPen)
|
||||
else:
|
||||
# 设置画笔
|
||||
painter.setPen(
|
||||
QPen(c, max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax))))
|
||||
|
||||
# 画实心圆或者圆圈
|
||||
painter.drawEllipse(circle.x - r, circle.y - r, 2 * r, 2 * r)
|
||||
painter.restore()
|
||||
|
||||
if circle.filled == 'concentric':
|
||||
r = radius / 2
|
||||
# 画圆圈
|
||||
painter.save()
|
||||
painter.setBrush(Qt.NoBrush)
|
||||
painter.setPen(
|
||||
QPen(c, max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax))))
|
||||
painter.drawEllipse(circle.x - r, circle.y - r, 2 * r, 2 * r)
|
||||
painter.restore()
|
||||
|
||||
circle.x += circle.speedx
|
||||
circle.y += circle.speedy
|
||||
if (circle.opacity < maxOpacity):
|
||||
circle.opacity += 0.01
|
||||
circle.ttl -= 1
|
||||
|
||||
def renderPoints(self, painter, circles):
|
||||
for i, circle in enumerate(circles):
|
||||
if circle.ttl < -20:
|
||||
# 重新初始化一个
|
||||
circle = Circle('', self.screenWidth, self.screenHeight)
|
||||
circles[i] = circle
|
||||
self.drawCircle(painter, circle)
|
||||
|
||||
circles_len = len(circles)
|
||||
for i in range(circles_len - 1):
|
||||
for j in range(i + 1, circles_len):
|
||||
deltax = circles[i].x - circles[j].x
|
||||
deltay = circles[i].y - circles[j].y
|
||||
dist = pow(pow(deltax, 2) + pow(deltay, 2), 0.5)
|
||||
# if the circles are overlapping, no laser connecting them
|
||||
if dist <= circles[i].radius + circles[j].radius:
|
||||
continue
|
||||
# otherwise we connect them only if the dist is < linkDist
|
||||
if dist < self.linkDist:
|
||||
xi = (1 if circles[i].x < circles[j].x else -
|
||||
1) * abs(circles[i].radius * deltax / dist)
|
||||
yi = (1 if circles[i].y < circles[j].y else -
|
||||
1) * abs(circles[i].radius * deltay / dist)
|
||||
xj = (-1 if circles[i].x < circles[j].x else 1) * \
|
||||
abs(circles[j].radius * deltax / dist)
|
||||
yj = (-1 if circles[i].y < circles[j].y else 1) * \
|
||||
abs(circles[j].radius * deltay / dist)
|
||||
path = QPainterPath()
|
||||
path.moveTo(circles[i].x + xi, circles[i].y + yi)
|
||||
path.lineTo(circles[j].x + xj, circles[j].y + yj)
|
||||
# samecolor = circles[i].color == circles[j].color
|
||||
c = QColor(circles[i].borderColor)
|
||||
c.setAlphaF(min(circles[i].opacity, circles[j].opacity)
|
||||
* ((self.linkDist - dist) / self.linkDist))
|
||||
painter.setPen(QPen(c, (
|
||||
lineBorder * backgroundMlt if circles[i].background else lineBorder) * (
|
||||
(self.linkDist - dist) / self.linkDist)))
|
||||
painter.drawPath(path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
app = QApplication(sys.argv)
|
||||
w = CircleLineWindow()
|
||||
w.resize(800, 600)
|
||||
w.show()
|
||||
sys.exit(app.exec_())
|
208
Demo/Data/背景连线动画.html
Normal file
208
Demo/Data/背景连线动画.html
Normal file
|
@ -0,0 +1,208 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>背景连线动画</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
background: #262b2e
|
||||
}
|
||||
#canvas {
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<canvas id="canvas" width="1920px" height="1080px"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
// min and max radius, radius threshold and percentage of filled circles
|
||||
var radMin = 10,
|
||||
radMax = 80,
|
||||
filledCircle = 30, //percentage of filled circles
|
||||
concentricCircle = 60, //percentage of concentric circles
|
||||
radThreshold = 25; //IFF special, over this radius concentric, otherwise filled
|
||||
|
||||
//min and max speed to move
|
||||
var speedMin = 0.3,
|
||||
speedMax = 0.6;
|
||||
|
||||
//max reachable opacity for every circle and blur effect
|
||||
var maxOpacity = 0.6;
|
||||
|
||||
//default palette choice
|
||||
var colors = ['52,168,83', '117,95,147', '199,108,23', '194,62,55', '0,172,212', '120,120,120'],
|
||||
circleBorder = 10,
|
||||
backgroundLine = colors[0];
|
||||
var backgroundMlt = 0.85;
|
||||
|
||||
//min distance for links
|
||||
var linkDist = Math.min(canvas.width, canvas.height) / 2.4,
|
||||
lineBorder = 2.5;
|
||||
|
||||
//most importantly: number of overall circles and arrays containing them
|
||||
var maxCircles = 8;
|
||||
var points = [];
|
||||
|
||||
|
||||
//experimental vars
|
||||
var circleExp = 1,
|
||||
circleExpMax = 1.003,
|
||||
circleExpMin = 0.997,
|
||||
circleExpSp = 0.00004,
|
||||
circlePulse = false;
|
||||
|
||||
var ctxfr = null;
|
||||
//circle class
|
||||
function Circle(background) {
|
||||
this.x = randRange(-canvas.width / 2, canvas.width / 2);
|
||||
this.y = randRange(-canvas.height / 2, canvas.height / 2);
|
||||
this.radius = hyperRange(radMin, radMax);
|
||||
this.filled = this.radius < radThreshold ? (randint(0, 100) > filledCircle ? false : 'full') : (randint(0, 100) > concentricCircle ? false : 'concentric');
|
||||
this.color = colors[randint(0, colors.length - 1)];
|
||||
this.borderColor = colors[randint(0, colors.length - 1)];
|
||||
this.opacity = 0.05;
|
||||
this.speed = randRange(speedMin, speedMax); // * (radMin / this.radius);
|
||||
this.speedAngle = Math.random() * 2 * Math.PI;
|
||||
this.speedx = Math.cos(this.speedAngle) * this.speed;
|
||||
this.speedy = Math.sin(this.speedAngle) * this.speed;
|
||||
var spacex = Math.abs((this.x - (this.speedx < 0 ? -1 : 1) * (canvas.width / 2 + this.radius)) / this.speedx),
|
||||
spacey = Math.abs((this.y - (this.speedy < 0 ? -1 : 1) * (canvas.height / 2 + this.radius)) / this.speedy);
|
||||
|
||||
this.ttl = Math.min(spacex, spacey);
|
||||
}
|
||||
|
||||
Circle.prototype.init = function() {
|
||||
Circle.call(this, this.background);
|
||||
};
|
||||
|
||||
//support functions
|
||||
//generate random int a<=x<=b
|
||||
function randint(a, b) {
|
||||
return Math.floor(Math.random() * (b - a + 1) + a);
|
||||
}
|
||||
//generate random float
|
||||
function randRange(a, b) {
|
||||
return Math.random() * (b - a) + a;
|
||||
}
|
||||
//generate random float more likely to be close to a
|
||||
function hyperRange(a, b) {
|
||||
return Math.random() * Math.random() * Math.random() * (b - a) + a;
|
||||
}
|
||||
|
||||
//rendering function
|
||||
function drawCircle(ctx, circle) {
|
||||
//circle.radius *= circleExp;
|
||||
var radius = circle.background ? circle.radius *= circleExp : circle.radius /= circleExp;
|
||||
ctx.beginPath();
|
||||
ctx.arc(circle.x, circle.y, radius * circleExp, 0, 2 * Math.PI, false);
|
||||
ctx.lineWidth = Math.max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax));
|
||||
ctx.strokeStyle = ['rgba(', circle.borderColor, ',', circle.opacity, ')'].join('');
|
||||
if (circle.filled === 'full') {
|
||||
ctx.fillStyle = ['rgba(', circle.borderColor, ',', circle.opacity, ')'].join('');
|
||||
ctx.fill();
|
||||
ctx.lineWidth=0;
|
||||
ctx.strokeStyle = ['rgba(', circle.borderColor, ',', 0, ')'].join('');
|
||||
}
|
||||
ctx.stroke();
|
||||
if (circle.filled === 'concentric') {
|
||||
ctx.beginPath();
|
||||
ctx.arc(circle.x, circle.y, radius / 2, 0, 2 * Math.PI, false);
|
||||
ctx.lineWidth = Math.max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax));
|
||||
ctx.strokeStyle = ['rgba(', circle.color, ',', circle.opacity, ')'].join('');
|
||||
ctx.stroke();
|
||||
}
|
||||
circle.x += circle.speedx;
|
||||
circle.y += circle.speedy;
|
||||
if (circle.opacity < maxOpacity) circle.opacity += 0.01;
|
||||
circle.ttl--;
|
||||
}
|
||||
|
||||
//initializing function
|
||||
function init() {
|
||||
|
||||
var canvas1 = document.getElementById('canvas');
|
||||
canvas1.width = document.documentElement.clientWidth;
|
||||
canvas1.height = document.documentElement.clientHeight;
|
||||
ctxfr = document.getElementById('canvas').getContext('2d');
|
||||
ctxfr.globalCompositeOperation = 'destination-over';
|
||||
//populating the screen
|
||||
for (var i = 0; i < maxCircles * 2; i++) points.push(new Circle(false));
|
||||
|
||||
window.requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
//rendering function
|
||||
function draw() {
|
||||
if (circlePulse) {
|
||||
if (circleExp < circleExpMin || circleExp > circleExpMax) circleExpSp *= -1;
|
||||
circleExp += circleExpSp;
|
||||
}
|
||||
|
||||
ctxfr.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
|
||||
ctxfr.save();
|
||||
ctxfr.translate(canvas.width / 2, canvas.height / 2);
|
||||
|
||||
|
||||
//function to render each single circle, its connections and to manage its out of boundaries replacement
|
||||
function renderPoints(ctx, arr) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var circle = arr[i];
|
||||
//checking if out of boundaries
|
||||
if (circle.ttl<0) {}
|
||||
var xEscape = canvas.width / 2 + circle.radius,
|
||||
yEscape = canvas.height / 2 + circle.radius;
|
||||
if (circle.ttl < -20) arr[i].init(arr[i].background);
|
||||
//if (Math.abs(circle.y) > yEscape || Math.abs(circle.x) > xEscape) arr[i].init(arr[i].background);
|
||||
drawCircle(ctx, circle);
|
||||
}
|
||||
for (var i = 0; i < arr.length - 1; i++) {
|
||||
for (var j = i + 1; j < arr.length; j++) {
|
||||
var deltax = arr[i].x - arr[j].x;
|
||||
var deltay = arr[i].y - arr[j].y;
|
||||
var dist = Math.pow(Math.pow(deltax, 2) + Math.pow(deltay, 2), 0.5);
|
||||
//if the circles are overlapping, no laser connecting them
|
||||
if (dist <= arr[i].radius + arr[j].radius) continue;
|
||||
//otherwise we connect them only if the dist is < linkDist
|
||||
if (dist < linkDist) {
|
||||
var xi = (arr[i].x < arr[j].x ? 1 : -1) * Math.abs(arr[i].radius * deltax / dist);
|
||||
var yi = (arr[i].y < arr[j].y ? 1 : -1) * Math.abs(arr[i].radius * deltay / dist);
|
||||
var xj = (arr[i].x < arr[j].x ? -1 : 1) * Math.abs(arr[j].radius * deltax / dist);
|
||||
var yj = (arr[i].y < arr[j].y ? -1 : 1) * Math.abs(arr[j].radius * deltay / dist);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(arr[i].x + xi, arr[i].y + yi);
|
||||
ctx.lineTo(arr[j].x + xj, arr[j].y + yj);
|
||||
var samecolor = arr[i].color == arr[j].color;
|
||||
ctx.strokeStyle = ["rgba(", arr[i].borderColor, ",", Math.min(arr[i].opacity, arr[j].opacity) * ((linkDist - dist) / linkDist), ")"].join("");
|
||||
ctx.lineWidth = (arr[i].background ? lineBorder * backgroundMlt : lineBorder) * ((linkDist - dist) / linkDist); //*((linkDist-dist)/linkDist);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var startTime = Date.now();
|
||||
renderPoints(ctxfr, points);
|
||||
|
||||
deltaT = Date.now() - startTime;
|
||||
ctxfr.restore();
|
||||
|
||||
window.requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
|
||||
|
||||
|
||||
/*Credits and aknowledgements:
|
||||
Original Idea and Design by Luca Luzzatti
|
||||
|
||||
Optimizing tips from Benjamin K?stner
|
||||
General tips from Salvatore Previti*/
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -16,6 +16,7 @@
|
|||
- [验证码控件](#14、验证码控件)
|
||||
- [人脸特征点](#15、人脸特征点)
|
||||
- [使用Threading](#16、使用Threading)
|
||||
- [背景连线动画](#17、背景连线动画)
|
||||
|
||||
## 1、重启窗口Widget
|
||||
[运行 RestartWindow.py](RestartWindow.py)
|
||||
|
@ -163,3 +164,10 @@ PyQt 结合 Opencv 进行人脸检测;
|
|||
在PyQt中使用Theading线程
|
||||
|
||||
![QtThreading](ScreenShot/QtThreading.gif)
|
||||
|
||||
## 17、背景连线动画
|
||||
[运行 CircleLine.py](CircleLine.py)
|
||||
|
||||
主要参考 [背景连线动画.html](Data/背景连线动画.html)
|
||||
|
||||
![CircleLine](ScreenShot/CircleLine.gif)
|
BIN
Demo/ScreenShot/CircleLine.gif
Normal file
BIN
Demo/ScreenShot/CircleLine.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 778 KiB |
|
@ -171,6 +171,7 @@ https://pyqt5.com 社区是专门针对PyQt5学习和提升开设的博客网站
|
|||
- [验证码控件](Demo/VerificationCode.py)
|
||||
- [人脸特征点](Demo/FacePoints.py)
|
||||
- [使用Threading](Demo/QtThreading.py)
|
||||
- [背景连线动画](Demo/CircleLine.py)
|
||||
|
||||
# QQ群
|
||||
|
||||
|
|
Loading…
Reference in a new issue