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() {