From bdc3181797f9271ad476228f7e003034aef5a60e Mon Sep 17 00:00:00 2001
From: Kirigaya <1193466151@qq.com>
Date: Mon, 30 Dec 2024 21:50:54 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=84=89=E5=86=B2=E7=89=B9?=
=?UTF-8?q?=E6=95=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/hook/render/wire.js | 143 +++++++++++++++++++++++++++++--------
src/hook/skin/plusation.js | 98 +++++++++++++------------
2 files changed, 166 insertions(+), 75 deletions(-)
diff --git a/src/hook/render/wire.js b/src/hook/render/wire.js
index 287731a..68b3339 100644
--- a/src/hook/render/wire.js
+++ b/src/hook/render/wire.js
@@ -5,6 +5,7 @@ import { globalSetting } from '../global';
import { NetlistRender } from '.';
import { LAYOUT_CONSTANT, LINE_WIDTH } from './layout';
import { svgResource } from '../skin/draw';
+import { PulseLine } from '../skin/plusation';
export class WireRender {
/**
@@ -121,6 +122,7 @@ export class WireRender {
arrowSelections.raise();
const id2animation = new Map();
+ const id2PluseLine = new Map();
lineSelections.on('click', function(_, data) {
data.active = !data.active;
@@ -128,39 +130,120 @@ export class WireRender {
if (data.active) {
const pathSelection = d3.select(this);
// 如果当前激活,显示数据流向
- const pathLength = pathSelection.node().getTotalLength();
+ // const pathLength = pathSelection.node().getTotalLength();
- const pivot = _this.selection
- .append('circle')
- .attr('r', 6)
- .attr('fill', 'var(--wire-ball-color)');
+ const g = _this.selection.append("g")
+ .attr("mask", "url(#m1)");
+
+ // 创建 mask
+ const mask = g.append("mask")
+ .attr('id', 'm1')
+
+ mask.append("use")
+ .attr("id", "u1")
+ .attr("href", "#p1")
+ .attr("stroke", "white")
+ .attr("stroke-width", 7)
+ .attr("fill", "none")
+ .attr('stroke-dasharray', '200 300')
+ .attr("stroke-linecap", "round");
+
+ // 创建 radialGradient
+ const radialGradient = g.append("radialGradient")
+ .attr("id", "g1");
+
+ radialGradient.append("stop")
+ .attr("offset", "0%")
+ .attr("stop-color", "white");
+
+ radialGradient.append("stop")
+ .attr("offset", "25%")
+ .attr("stop-color", "#CB81DA");
+
+ radialGradient.append("stop")
+ .attr("offset", "95%")
+ .attr("stop-opacity", 0)
+ .attr("stop-color", "rgb(70, 70, 222)");
+
+ // 创建 path
+ g.append("path")
+ .attr("id", "p1")
+ .attr('stroke-width', 10)
+ .attr("fill", "none")
+ .attr("d", data.svg);
+
+ // 创建 circle 和 animateMotion
+ g.append("circle")
+ .attr("r", 100)
+ .attr("fill", "url(#g1)")
+ .append("animateMotion")
+ .attr("dur", "3000ms")
+ .attr("repeatCount", "indefinite")
+ .append("mpath")
+ .attr("href", "#p1");
+
+ let p1 = document.getElementById('p1');
+ let u1 = document.getElementById('u1');
+
+ const pathLength = p1.getTotalLength();
- // 进行一次动画
- function renderOneAnimation() {
- console.log(pathLength);
- // 1400 的长度差不多配 3500 ?
- const duration = pathLength / 14 * 35;
- let transition = pivot.transition().duration(3500);
- transition.attrTween('transform', () => {
- return t => {
- const x = cubicBezierAnimation(t, 0, pathLength);
- const point = pathSelection.node().getPointAtLength(x);
- return `translate(${point.x},${point.y})`;
- }
- });
- transition = transition.on('end', function() {
- if (id2animation.has(data.id)) {
- renderOneAnimation();
- }
- });
- // 动画结束后重新启动动画
- id2animation.set(data.id, {
- pivot,
- transition
- });
- }
+ var u1Keyframes = new KeyframeEffect(
+ u1, [{
+ strokeDasharray: `200 ${pathLength - 200}`,
+ strokeDashoffset: "200"
+ },
+ {
+ strokeDasharray: `200 ${pathLength}`,
+ strokeDashoffset: `-${pathLength - 200}`
+ }
+ ], {
+ duration: 3000,
+ iterations: Infinity
+ }
+ );
+
+ const pulsationAnimation = new Animation(u1Keyframes, document.timeline);
+
+ requestAnimationFrame(() => {
+ const motion = g.selectAll('animateMotion').node();
+ console.log(motion);
+ motion.setAttribute('begin', '0s');
+ motion.beginElement();
+
+ pulsationAnimation.play();
+ });
- renderOneAnimation();
+ // const pivot = _this.selection
+ // .append('circle')
+ // .attr('r', 6)
+ // .attr('fill', 'var(--wire-ball-color)');
+
+ // // 进行一次动画
+ // function renderOneAnimation() {
+ // console.log(pathLength);
+ // // 1400 的长度差不多配 3500 ?
+ // const duration = pathLength / 14 * 35;
+ // let transition = pivot.transition().duration(3500);
+ // transition.attrTween('transform', () => {
+ // return t => {
+ // const x = cubicBezierAnimation(t, 0, pathLength);
+ // const point = pathSelection.node().getPointAtLength(x);
+ // return `translate(${point.x},${point.y})`;
+ // }
+ // });
+ // transition = transition.on('end', function() {
+ // if (id2animation.has(data.id)) {
+ // renderOneAnimation();
+ // }
+ // });
+ // // 动画结束后重新启动动画
+ // id2animation.set(data.id, {
+ // pivot,
+ // transition
+ // });
+ // }
+
+ // renderOneAnimation();
} else {
if (id2animation.has(data.id)) {
const { pivot, transition } = id2animation.get(data.id);
diff --git a/src/hook/skin/plusation.js b/src/hook/skin/plusation.js
index 09e003c..97efb03 100644
--- a/src/hook/skin/plusation.js
+++ b/src/hook/skin/plusation.js
@@ -1,65 +1,73 @@
+import * as d3 from 'd3';
+
let PluseIDCount = 0;
-class PulseLine {
+export class PulseLine {
constructor() {
- // 维护这部分的 id
- PluseIDCount++;
- this.pluseId = PluseIDCount;
}
- loadData(pathString) {
- this.path = pathString
+ loadData(pathString, id) {
+ this.path = pathString;
+ this.pluseId = id;
}
/**
- * @param {PulseLineSvgConfig} config
- * @returns
+ *
+ * @param {d3.Selection} parentSelection
*/
- svgDoc(config) {
+ loadToSelection(parentSelection, config) {
config = config || {};
- const pluseStartColor = config.pluseStartColor || 'white';
- const pluseMiddleColor = config.pluseMiddleColor || '#CB81DA';
- const pluseEndColor = config.pluseEndColor || 'rgb(70, 70, 222)';
- const transform = config.transform || 'translate(0 0)';
- const duration = config.duration || 5000;
-
- this.duration = duration;
-
+ const pluse = parentSelection.append('g');
+
const gId = 'pluse-g' + this.pluseId;
const pId = 'pluse-p' + this.pluseId;
const mId = 'pluse-m' + this.pluseId;
const uId = 'pluse-u' + this.pluseId;
- const pathString = this.path;
- const parser = new DOMParser();
+ pluse.append('radialGradient')
+ .attr("id", gId)
+ .selectAll("stop")
+ .data([
+ { offset: "0%", color: "white" },
+ { offset: "25%", color: "#CB81DA" },
+ { offset: "95%", color: "rgb(70, 70, 222)", opacity: 0 }
+ ])
+ .enter()
+ .append("stop")
+ .attr("offset", d => d.offset)
+ .attr("stop-color", d => d.color)
+ .attr("stop-opacity", d => d.opacity || 1);
- const svgString = ``;
+ pluse.append("path")
+ .attr("id", pId)
+ .attr('fill', 'none')
+ .attr("d", "M133,613 L470,613 L470,515.5 L661,515.5");
+
+ const mask = pluse.append("mask")
+ .attr("id", mId);
- const svgDoc = parser.parseFromString(svgString, 'image/svg+xml');
- return svgDoc;
+ mask.append("use")
+ .attr("id", uId)
+ .attr("href", pId)
+ .attr("stroke", "white")
+ .attr("stroke-width", 5)
+ .attr("fill", "none")
+ .attr("stroke-dasharray", "100 200")
+ .attr("stroke-linecap", "round");
+
+ // 创建使用 mask 和 radialGradient 的
+ const maskedG = pluse.append("g")
+ .attr("mask", `url(#${mId})`);
+
+ // 创建 circle 和 animateMotion
+ maskedG.append("circle")
+ .attr("r", 50)
+ .attr("fill", `url(#${pId})`)
+ .append("animateMotion")
+ .attr("dur", "4500ms")
+ .attr("repeatCount", "indefinite")
+ .append("mpath")
+ .attr("href", `#${pId}`);
}
animation() {