背景连线动画

This commit is contained in:
Irony 2019-03-20 23:39:38 +08:00
parent 4207ec86eb
commit 8135d21160
6 changed files with 482 additions and 1 deletions

View file

@ -1,5 +1,6 @@
eclipse.preferences.version=1 eclipse.preferences.version=1
encoding//Demo/AutoRestart.py=utf-8 encoding//Demo/AutoRestart.py=utf-8
encoding//Demo/CircleLine.py=utf-8
encoding//Demo/EmbedWindow.py=utf-8 encoding//Demo/EmbedWindow.py=utf-8
encoding//Demo/FacePoints.py=utf-8 encoding//Demo/FacePoints.py=utf-8
encoding//Demo/FollowWindow.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/AwesomeFont.py=utf-8
encoding//QFont/Lib/FontAwesome.py=utf-8 encoding//QFont/Lib/FontAwesome.py=utf-8
encoding//QGraphicsDropShadowEffect/ShadowEffect.py=utf-8 encoding//QGraphicsDropShadowEffect/ShadowEffect.py=utf-8
encoding//QGraphicsView/WorldMap.py=utf-8
encoding//QListView/CustomWidgetSortItem.py=utf-8 encoding//QListView/CustomWidgetSortItem.py=utf-8
encoding//QListView/SortItemByRole.py=utf-8 encoding//QListView/SortItemByRole.py=utf-8
encoding//QMessageBox/CustomColorIcon.py=utf-8 encoding//QMessageBox/CustomColorIcon.py=utf-8

262
Demo/CircleLine.py Normal file
View 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_())

View 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>

View file

@ -16,6 +16,7 @@
- [验证码控件](#14、验证码控件) - [验证码控件](#14、验证码控件)
- [人脸特征点](#15、人脸特征点) - [人脸特征点](#15、人脸特征点)
- [使用Threading](#16、使用Threading) - [使用Threading](#16、使用Threading)
- [背景连线动画](#17、背景连线动画)
## 1、重启窗口Widget ## 1、重启窗口Widget
[运行 RestartWindow.py](RestartWindow.py) [运行 RestartWindow.py](RestartWindow.py)
@ -162,4 +163,11 @@ PyQt 结合 Opencv 进行人脸检测;
在PyQt中使用Theading线程 在PyQt中使用Theading线程
![QtThreading](ScreenShot/QtThreading.gif) ![QtThreading](ScreenShot/QtThreading.gif)
## 17、背景连线动画
[运行 CircleLine.py](CircleLine.py)
主要参考 [背景连线动画.html](Data/背景连线动画.html)
![CircleLine](ScreenShot/CircleLine.gif)

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 KiB

View file

@ -171,6 +171,7 @@ https://pyqt5.com 社区是专门针对PyQt5学习和提升开设的博客网站
- [验证码控件](Demo/VerificationCode.py) - [验证码控件](Demo/VerificationCode.py)
- [人脸特征点](Demo/FacePoints.py) - [人脸特征点](Demo/FacePoints.py)
- [使用Threading](Demo/QtThreading.py) - [使用Threading](Demo/QtThreading.py)
- [背景连线动画](Demo/CircleLine.py)
# QQ群 # QQ群