From 3607e66bb357914976c53704f274a2d510501d39 Mon Sep 17 00:00:00 2001
From: Tunglies <77394545+Tunglies@users.noreply.github.com>
Date: Sat, 3 Oct 2026 06:57:46 +0800
Subject: [PATCH] perf(traffic): reuse canvas pixels while scrolling
---
Cargo.lock | 2 +-
Changelog.md | 25 +----
docs/Changelog.history.md | 37 ++++++++
package.json | 2 +-
src-tauri/Cargo.toml | 2 +-
src-tauri/tauri.conf.json | 2 +-
src/components/layout/traffic-graph.tsx | 118 +++++++++---------------
7 files changed, 89 insertions(+), 99 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 62a8027fd..084a1954e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1079,7 +1079,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "clash-verge"
-version = "2.5.7"
+version = "2.5.8"
dependencies = [
"aes-gcm",
"anyhow",
diff --git a/Changelog.md b/Changelog.md
index 405650100..b23eae369 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,35 +1,20 @@
-## v2.5.7
+## v2.5.8
🐞 修复问题
-- 修复读屏软件将普通模式下的侧边栏导航项播报为不可用的可拖拽控件的问题
-- 修复订阅包含自定义 DNS 时,确认开启的 DNS 覆写在重启应用后被自动关闭的问题
-**🖥️ Windows**
+
-- 修复 Windows 系统盘根目录的删除权限被误判,导致服务模式和 TUN 无法使用的问题
-- 修复 Windows 部分系统安装服务失败,提示「program not found」的问题
+
+ ✨ 新增功能
-**🍎 macOS**
-
-- macOS 修复启动或重启应用时偶发内核启动失败,并误提示「需要更新系统服务」的问题
-- macOS 修复 VPN 接管网络或开机网络未就绪时的问题:服务模式内核无法启动、TUN 不可用、系统代理状态读取报错
-- macOS 修复用 `ipconfig set` 手动配置网卡后无法设置系统代理的问题
-
-**🍎/🐧 macOS/Linux**
-
-- 修复 macOS/Linux 服务模式停止或重启内核时直接强制结束的问题:内核来不及正常退出、最后的日志丢失
🚀 优化改进
-- 优化服务模式的问题排查:服务自身的运行日志会保存到文件,便于追查内核被停止的原因
-
-**🖥️ Windows**
-
-- 优化 Windows 服务安全检查未通过时的启动提示:不再只显示「服务无法启动内核」,直接说明原因并提供修复文档链接
+- 优化侧边栏流量图表的 CPU 占用
diff --git a/docs/Changelog.history.md b/docs/Changelog.history.md
index b834c2b9d..33b61d646 100644
--- a/docs/Changelog.history.md
+++ b/docs/Changelog.history.md
@@ -1,3 +1,40 @@
+## v2.5.7
+
+
+ 🐞 修复问题
+
+- 修复读屏软件将普通模式下的侧边栏导航项播报为不可用的可拖拽控件的问题
+- 修复订阅包含自定义 DNS 时,确认开启的 DNS 覆写在重启应用后被自动关闭的问题
+
+**🖥️ Windows**
+
+- 修复 Windows 系统盘根目录的删除权限被误判,导致服务模式和 TUN 无法使用的问题
+- 修复 Windows 部分系统安装服务失败,提示「program not found」的问题
+
+**🍎 macOS**
+
+- macOS 修复启动或重启应用时偶发内核启动失败,并误提示「需要更新系统服务」的问题
+- macOS 修复 VPN 接管网络或开机网络未就绪时的问题:服务模式内核无法启动、TUN 不可用、系统代理状态读取报错
+- macOS 修复用 `ipconfig set` 手动配置网卡后无法设置系统代理的问题
+
+**🍎/🐧 macOS/Linux**
+
+- 修复 macOS/Linux 服务模式停止或重启内核时直接强制结束的问题:内核来不及正常退出、最后的日志丢失
+
+
+
+
+ 🚀 优化改进
+
+- 优化服务模式的问题排查:服务自身的运行日志会保存到文件,便于追查内核被停止的原因
+
+**🖥️ Windows**
+
+- 优化 Windows 服务安全检查未通过时的启动提示:不再只显示「服务无法启动内核」,直接说明原因并提供修复文档链接
+
+
+
+
## v2.5.6
diff --git a/package.json b/package.json
index bb8355a8a..47217b9be 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "clash-verge",
- "version": "2.5.7",
+ "version": "2.5.8",
"license": "GPL-3.0-only",
"scripts": {
"prepare": "husky || true",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 1a6f26231..9d88820e6 100755
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
-version = "2.5.7"
+version = "2.5.8"
description = "clash verge"
authors = ["zzzgydi", "Tunglies", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 763b5163a..2231069d5 100755
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,5 +1,5 @@
{
- "version": "2.5.7",
+ "version": "2.5.8",
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"bundle": {
"active": true,
diff --git a/src/components/layout/traffic-graph.tsx b/src/components/layout/traffic-graph.tsx
index 2b4572db7..f6893003b 100644
--- a/src/components/layout/traffic-graph.tsx
+++ b/src/components/layout/traffic-graph.tsx
@@ -13,7 +13,6 @@ const upLineWidth = 4
const downLineAlpha = 1
const downLineWidth = 4
const sampleIntervalMs = 1000
-const frameIntervalMs = 1000 / 15
const animationDurationMs = sampleIntervalMs
const zeroTraffic: Traffic = { up: 0, down: 0, upTotal: 0, downTotal: 0 }
@@ -36,7 +35,7 @@ type TrafficValueKey = 'up' | 'down'
* draw the traffic graph
*/
export function TrafficGraph({ ref }: { ref?: Ref }) {
- const countRef = useRef(0)
+ const animationStartRef = useRef(null)
const styleRef = useRef(true)
const listRef = useRef(createDefaultList())
const canvasRef = useRef(null!)
@@ -69,7 +68,6 @@ export function TrafficGraph({ ref }: { ref?: Ref }) {
if (shouldAppend) {
if (list.length > maxPoint + 2) list.shift()
list.push(data)
- countRef.current = 0
requestDrawRef.current(true)
}
@@ -84,11 +82,8 @@ export function TrafficGraph({ ref }: { ref?: Ref }) {
}, [])
useEffect(() => {
- let raf = 0
- let frameTimer: ReturnType | null = null
+ let animation: Animation | null = null
let resizeObserver: ResizeObserver | null = null
- let animationStart = 0
- let lastFrameTime = 0
const canvas = canvasRef.current!
if (!canvas) return
@@ -102,25 +97,13 @@ export function TrafficGraph({ ref }: { ref?: Ref }) {
const upLineColor = secondary.main || '#9c27b0'
const downLineColor = primary.main || '#5b5c9d'
- const cancelPendingDraw = () => {
- if (frameTimer !== null) {
- clearTimeout(frameTimer)
- frameTimer = null
- }
-
- if (raf) {
- cancelAnimationFrame(raf)
- raf = 0
- }
- }
-
- const drawGraph = (offset = countRef.current) => {
+ const drawGraph = () => {
const list = listRef.current
const lineStyle = styleRef.current
const width = canvas.width
const height = canvas.height
- const dx = width / maxPoint
+ const dx = width / (maxPoint + 1)
const dy = height / 7
const l1 = dy
const l2 = dy * 4
@@ -142,17 +125,17 @@ export function TrafficGraph({ ref }: { ref?: Ref }) {
const drawBezier = (list: Traffic[], valueKey: TrafficValueKey) => {
if (list.length === 0) return
- const firstX = (dx * -1 - offset + 3) | 0
+ const firstX = (dx * -1 + 3) | 0
const firstY = countY(list[0]?.[valueKey] ?? 0)
context.moveTo(firstX, firstY)
for (let i = 1; i < list.length; i++) {
- const p1x = (dx * (i - 1) - offset + 3) | 0
+ const p1x = (dx * (i - 1) + 3) | 0
const p1y = countY(list[i]?.[valueKey] ?? 0)
const hasNext = i + 1 < list.length
- const p2x = hasNext ? (dx * i - offset + 3) | 0 : p1x
+ const p2x = hasNext ? (dx * i + 3) | 0 : p1x
const p2y = hasNext ? countY(list[i + 1]?.[valueKey] ?? 0) : p1y
context.quadraticCurveTo(p1x, p1y, (p1x + p2x) / 2, (p1y + p2y) / 2)
@@ -162,13 +145,10 @@ export function TrafficGraph({ ref }: { ref?: Ref }) {
const drawLine = (list: Traffic[], valueKey: TrafficValueKey) => {
if (list.length === 0) return
- context.moveTo((dx * -1 - offset) | 0, countY(list[0]?.[valueKey] ?? 0))
+ context.moveTo((dx * -1) | 0, countY(list[0]?.[valueKey] ?? 0))
for (let i = 1; i < list.length; i++) {
- context.lineTo(
- (dx * (i - 1) - offset) | 0,
- countY(list[i]?.[valueKey] ?? 0),
- )
+ context.lineTo((dx * (i - 1)) | 0, countY(list[i]?.[valueKey] ?? 0))
}
}
@@ -211,51 +191,28 @@ export function TrafficGraph({ ref }: { ref?: Ref }) {
context.closePath()
}
- const drawAnimatedFrame = (timestamp: number) => {
- raf = 0
-
- const timeSinceLastFrame = timestamp - lastFrameTime
- if (timeSinceLastFrame < frameIntervalMs) {
- frameTimer = setTimeout(() => {
- frameTimer = null
- raf = requestAnimationFrame(drawAnimatedFrame)
- }, frameIntervalMs - timeSinceLastFrame)
- return
- }
-
- lastFrameTime = timestamp
-
- const dx = canvas.width / maxPoint
- const progress = Math.min(
- (timestamp - animationStart) / animationDurationMs,
- 1,
- )
- const offset = progress * dx
- countRef.current = offset
- drawGraph(offset)
-
- if (progress < 1) {
- raf = requestAnimationFrame(drawAnimatedFrame)
- return
- }
-
- countRef.current = dx
- }
-
const requestDraw = (animate = false) => {
- cancelPendingDraw()
+ if (animate) animationStartRef.current = performance.now()
+ animation?.cancel()
+ drawGraph()
- if (!animate) {
- raf = requestAnimationFrame(() => {
- raf = 0
- drawGraph()
- })
- return
- }
-
- animationStart = performance.now()
- lastFrameTime = animationStart - frameIntervalMs
- raf = requestAnimationFrame(drawAnimatedFrame)
+ const elapsed =
+ animationStartRef.current === null
+ ? 0
+ : Math.min(
+ performance.now() - animationStartRef.current,
+ animationDurationMs,
+ )
+ const step = canvas.getBoundingClientRect().width / (maxPoint + 1)
+ animation = canvas.animate(
+ [
+ { transform: 'translateX(0)' },
+ { transform: `translateX(${-step}px)` },
+ ],
+ { duration: animationDurationMs, easing: 'linear', fill: 'forwards' },
+ )
+ animation.currentTime = elapsed
+ if (animationStartRef.current === null) animation.pause()
}
requestDrawRef.current = requestDraw
@@ -263,7 +220,7 @@ export function TrafficGraph({ ref }: { ref?: Ref }) {
if (typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(() => requestDraw(false))
- resizeObserver.observe(canvas)
+ resizeObserver.observe(canvas.parentElement!)
}
return () => {
@@ -271,9 +228,20 @@ export function TrafficGraph({ ref }: { ref?: Ref }) {
requestDrawRef.current = () => {}
}
resizeObserver?.disconnect()
- cancelPendingDraw()
+ animation?.cancel()
}
}, [palette])
- return
+ return (
+
+
+
+ )
}