雑ですがSVGでサンプル作ってみました
javascript
1<style>
2svg{background-Color:lightgray;}
3</style>
4<script>
5
6window.addEventListener('DOMContentLoaded', ()=>{
7 const svgNS = svg.namespaceURI;
8 const points=[[100,100],[500,100],[400,300],[0,300]];
9 const createContainer=(points=[],fill=null)=>{
10 const container=document.createElementNS(svgNS,"polygon");
11 container.setAttributeNS(null,'fill',fill);
12 const pointsStr=points.map(x=>x.join(",")).join(" ");
13 container.setAttributeNS(null,"points",pointsStr);
14 return container;
15 }
16 const container=createContainer(points,"yellow");
17 svg.appendChild(container);
18 const lines=points.reduce((x,y,z,w)=>(x.push([y,w[z==w.length-1?0:z+1]]),x),[]);
19 const createMember=(options)=>{
20 const position=[options.x,options.y,null]
21 const member=document.createElementNS(svgNS,'rect');
22 member.setAttribute('x',options.x);
23 member.setAttribute('y',options.y);
24 member.setAttribute('width',options.width);
25 member.setAttribute('height',options.height);
26 member.setAttribute('fill',options.fill);
27 const path=document.createElementNS(svgNS,'path');
28 path.setAttributeNS(null,'id',options.id);
29 const motion=document.createElementNS(svgNS,'animateMotion');
30 motion.setAttributeNS(null,'fill','freeze');
31 const mpath=document.createElementNS(svgNS,'mpath');
32 mpath.setAttributeNS('http://www.w3.org/1999/xlink','xlink:href',`#${options.id}`);
33 svg.appendChild(member);
34 member.appendChild(path);
35 member.appendChild(motion);
36 motion.appendChild(mpath);
37 let timerId;
38 let timer;
39 let delay;
40 const callback=()=>{
41 timer=new Date().getTime();
42 motion.endElement();
43 let rnd=parseInt(Math.random()*lines.length);
44 while(rnd==position[2]){
45 rnd=parseInt(Math.random()*lines.length);//再抽選
46 }
47 position[2]=rnd;
48 let ratio=Math.random();
49 let x=parseInt((lines[rnd][1][0]-lines[rnd][0][0])*ratio+lines[rnd][0][0]);
50 let y=parseInt((lines[rnd][1][1]-lines[rnd][0][1])*ratio+lines[rnd][0][1]);
51 let w=options.width;
52 let h=options.height;
53 path.setAttributeNS(null,"d",`M${position[0]},${position[1]} L${x-w/2},${y-h}`);
54 //path.setAttributeNS(null,"stroke","black");
55 const distance=((position[0]-x)**2+(position[1]-y)**2)**.5;
56 position[0]=x;
57 position[1]=y;
58 motion.setAttributeNS(null,"dur",parseInt(options.speed*distance/10)/100);
59 motion.beginElement();
60 delay=options.speed*distance;
61 timerId=setTimeout(()=>callback(),delay);
62 }
63 btn_start.addEventListener('click',()=>{
64 member.removeAttributeNS(null,'x');
65 member.removeAttributeNS(null,'y');
66 callback();
67 btn_stop.disabled=false;
68 btn_start.disabled=true;
69 });
70 btn_stop.addEventListener('click',()=>{
71 clearTimeout(timerId);
72 const past=new Date().getTime()-timer;
73 const d=path.getAttributeNS(null,'d');
74 const reg=/^M(\d+),(\d+) +L(\d+),(\d+)/;
75 if(reg.test(d)){
76 const matches=d.match(reg);
77 const x=parseInt((Number(matches[1])+(matches[3]-matches[1])*past/delay)*100)/100;
78 const y=parseInt((Number(matches[2])+(matches[4]-matches[2])*past/delay)*100)/100;
79 position[0]=x;
80 position[1]=y;
81 }
82 motion.endElement();
83 btn_start.disabled=false;
84 btn_stop.disabled=true;
85 });
86 motion.beginElement();
87 return member;
88 }
89 const m=createMember({x:150,y:200,width:10,height:10,fill:'red',id:'path1',speed:3});
90 createMember({x:250,y:200,width:20,height:20,fill:'blue',id:'path2',speed:2});
91 createMember({x:350,y:200,width:10,height:15,fill:'green',id:'path4',speed:1});
92 console.log(m);
93});
94</script>
95<svg width=500 height=400 id="svg">
96</svg>
97<input type="button" value="start" id="btn_start">
98<input type="button" value="stop" id="btn_stop" disabled>