增加脉冲特效
This commit is contained in:
parent
04b56a5181
commit
bdc3181797
@ -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);
|
||||
|
@ -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 = `<svg viewBox="0 0 300 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="${gId}">
|
||||
<stop offset="0%" stop-color="${pluseStartColor}" />
|
||||
<stop offset="25%" stop-color="${pluseMiddleColor}" />
|
||||
<stop offset="95%" stop-opacity="0" stop-color="${pluseEndColor}" />
|
||||
</radialGradient>
|
||||
<mask id="${mId}">
|
||||
<use id="${uId}" href="#${pId}"
|
||||
stroke="white" stroke-width="5" fill="none"
|
||||
stroke-dasharray="100 200" stroke-linecap="round" />
|
||||
</mask>
|
||||
<path id="${pId}" d="${pathString}" />
|
||||
</defs>
|
||||
<use href="#${pId}" transform="${transform}" stroke="#CB81DA"
|
||||
stroke-width="1" fill="rgba(203, 129, 218, 0.1)"/>
|
||||
<g transform="${transform}" mask="url(#${mId})">
|
||||
<circle r="50" fill="url(#${gId})">
|
||||
<animateMotion dur="5000ms" repeatCount="indefinite">
|
||||
<mpath href="#${pId}" />
|
||||
</animateMotion>
|
||||
</circle>
|
||||
</g>
|
||||
</svg>`;
|
||||
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 的 <g>
|
||||
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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user