import * as d3 from 'd3'; let PluseIDCount = 0; export class PulseLine { constructor() { this.animation = undefined; this.pluseId = undefined; } /** * * @param {d3.Selection} parentSelection */ loadToSelection(parentSelection, data) { this.pluseId = data.id; const gId = 'pluse-g' + data.id; const pId = 'pluse-p' + data.id; const mId = 'pluse-m' + data.id; const uId = 'pluse-u' + data.id; const g = parentSelection.append("g") .attr("mask", `url(#${mId})`); this.g = g; // 创建 mask const mask = parentSelection.append("mask") .attr('id', mId); const maskPathSelection = mask.append("use") .attr("id", uId) .attr("href", "#" + pId) .attr("stroke", "white") .attr("stroke-width", 7) .attr("fill", "none") .attr('stroke-dasharray', '200 300') .attr("stroke-linecap", "round"); this.maskPathSelection = maskPathSelection; // 创建 radialGradient const radialGradient = g.append("radialGradient") .attr("id", gId); 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 const pathSelection = g.append("path") .attr("id", pId) .attr('stroke-width', 10) .attr("fill", "none") .attr("d", data.svg); this.pathSelection = pathSelection; // 创建 circle 和 animateMotion g.append("circle") .attr("r", 100) .attr("fill", `url(#${gId})`) .append("animateMotion") .attr("dur", "3000ms") .attr("repeatCount", "indefinite") .append("mpath") .attr("href", "#" + pId); } startAnimation() { if (this.animation) { requestAnimationFrame(() => { const motionElement = this.g.selectAll('animateMotion').node(); motionElement.setAttribute('begin', '0s'); motionElement.beginElement(); this.animation.play(); }); } else { const pathElement = this.pathSelection.node(); const pluseElement = this.maskPathSelection.node(); const pathLength = pathElement.getTotalLength(); const keyframes = new KeyframeEffect( pluseElement, [{ strokeDasharray: `200 ${pathLength - 200}`, strokeDashoffset: "200" }, { strokeDasharray: `200 ${pathLength}`, strokeDashoffset: `-${pathLength - 200}` } ], { duration: 3000, iterations: Infinity } ); const pulsationAnimation = new Animation(keyframes, document.timeline); this.animation = pulsationAnimation; requestAnimationFrame(() => { const motionElement = this.g.selectAll('animateMotion').node(); motionElement.setAttribute('begin', '0s'); motionElement.beginElement(); pulsationAnimation.play(); }); } } pauseAnimation() { if (!this.animation) { return; } this.animation.pause(); } destory() { if (this.g) { this.g.selectAll('*').remove(); } } } /** * @typedef PulseLineSvgConfig * @property {string} pluseStartColor * @property {string} pluseMiddleColor * @property {string} pluseEndColor * @property {string} transform * @property {number} duration */