增加脉冲特效

This commit is contained in:
锦恢 2024-12-30 22:13:29 +08:00
parent bdc3181797
commit ebb45b12a1
2 changed files with 117 additions and 192 deletions

View File

@ -129,126 +129,16 @@ export class WireRender {
if (data.active) { if (data.active) {
const pathSelection = d3.select(this); const pathSelection = d3.select(this);
// 如果当前激活,显示数据流向 const pulse = new PulseLine();
// const pathLength = pathSelection.node().getTotalLength(); id2PluseLine.set(data.id, pulse);
const g = _this.selection.append("g") pulse.loadToSelection(_this.selection, data);
.attr("mask", "url(#m1)"); pulse.startAnimation();
// 创建 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();
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();
});
// 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 { } else {
if (id2animation.has(data.id)) { if (id2PluseLine.has(data.id)) {
const { pivot, transition } = id2animation.get(data.id); const pulse = id2PluseLine.get(data.id);
pivot.remove(); pulse.destory();
id2animation.delete(data.id); id2PluseLine.delete(data.id);
} }
} }
}); });

View File

@ -4,98 +4,133 @@ let PluseIDCount = 0;
export class PulseLine { export class PulseLine {
constructor() { constructor() {
this.animation = undefined;
this.pluseId = undefined;
} }
loadData(pathString, id) {
this.path = pathString;
this.pluseId = id;
}
/** /**
* *
* @param {d3.Selection} parentSelection * @param {d3.Selection} parentSelection
*/ */
loadToSelection(parentSelection, config) { loadToSelection(parentSelection, data) {
config = config || {}; this.pluseId = data.id;
const pluse = parentSelection.append('g');
const gId = 'pluse-g' + this.pluseId; const gId = 'pluse-g' + data.id;
const pId = 'pluse-p' + this.pluseId; const pId = 'pluse-p' + data.id;
const mId = 'pluse-m' + this.pluseId; const mId = 'pluse-m' + data.id;
const uId = 'pluse-u' + this.pluseId; const uId = 'pluse-u' + data.id;
pluse.append('radialGradient') const g = parentSelection.append("g")
.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);
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);
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 的 <g>
const maskedG = pluse.append("g")
.attr("mask", `url(#${mId})`); .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 // 创建 circle 和 animateMotion
maskedG.append("circle") g.append("circle")
.attr("r", 50) .attr("r", 100)
.attr("fill", `url(#${pId})`) .attr("fill", `url(#${gId})`)
.append("animateMotion") .append("animateMotion")
.attr("dur", "4500ms") .attr("dur", "3000ms")
.attr("repeatCount", "indefinite") .attr("repeatCount", "indefinite")
.append("mpath") .append("mpath")
.attr("href", `#${pId}`); .attr("href", "#" + pId);
} }
animation() { startAnimation() {
const pId = 'pluse-p' + this.pluseId; if (this.animation) {
const uId = 'pluse-u' + this.pluseId; requestAnimationFrame(() => {
const motionElement = this.g.selectAll('animateMotion').node();
const pathElement = document.getElementById(pId); motionElement.setAttribute('begin', '0s');
const pluseElement = document.getElementById(uId); motionElement.beginElement();
this.animation.play();
const pathLength = pathElement.getTotalLength(); });
} else {
const keyframes = new KeyframeEffect( const pathElement = this.pathSelection.node();
pluseElement, [{ const pluseElement = this.maskPathSelection.node();
strokeDasharray: `100 ${pathLength}`,
strokeDashoffset: "100" const pathLength = pathElement.getTotalLength();
},
{ const keyframes = new KeyframeEffect(
strokeDasharray: `100 ${pathLength}`, pluseElement, [{
strokeDashoffset: `-${pathLength - 100}` strokeDasharray: `200 ${pathLength - 200}`,
strokeDashoffset: "200"
},
{
strokeDasharray: `200 ${pathLength}`,
strokeDashoffset: `-${pathLength - 200}`
}
], {
duration: 3000,
iterations: Infinity
} }
], { );
duration: this.duration,
iterations: Infinity const pulsationAnimation = new Animation(keyframes, document.timeline);
} this.animation = pulsationAnimation;
);
const pulsationAnimation = new Animation(keyframes, document.timeline); requestAnimationFrame(() => {
return pulsationAnimation; 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();
}
} }
} }