add xanadu link

This commit is contained in:
“chrisshank” 2024-11-26 12:27:30 -08:00
parent f4f51de53e
commit 55568be0e3
3 changed files with 382 additions and 17 deletions

272
demo/xanadu.html Normal file
View File

@ -0,0 +1,272 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Perfect Arrow - Xanadu</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet" />
<style>
* {
box-sizing: border-box;
}
html {
height: 100%;
font-family: 'Inter', sans-serif;
font-optical-sizing: auto;
/* font-weight: <weight>; */
font-style: normal;
font-variation-settings: 'slnt' 0;
}
body {
background-color: #f0f0f0;
margin: 0;
height: 100%;
position: relative;
overflow-y: hidden;
}
fc-geometry {
background-color: #fefefe;
width: 400px;
height: 100%;
article {
height: 100%;
padding: 2rem;
overflow: scroll;
text-align: justify;
}
h2 {
margin: 0;
}
}
folk-xanadu {
display: block;
position: absolute;
inset: 0 0 0 0;
pointer-events: none;
background-color: rgba(185 233 219 / 75%);
}
</style>
</head>
<body>
<fc-geometry name="1" x="20">
<article>
<h2>Note 1</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.
<span link="2"
>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
laborum.</span
>
</p>
<p link="1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
</article>
</fc-geometry>
<fc-geometry name="2" x="500">
<article>
<h2>Note 2</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing
<span link="1">
elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit</span
>
anim id est laborum.
</p>
<p>
<span link="2"
>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua.</span
>
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</article>
</fc-geometry>
<folk-xanadu source="fc-geometry[name='1'] [link='1']" target="fc-geometry[name='2'] [link='1']"></folk-xanadu>
<folk-xanadu source="fc-geometry[name='1'] [link='2']" target="fc-geometry[name='2'] [link='2']"></folk-xanadu>
<script type="module">
import { FolkXanadu } from '../src/folk-xanadu.ts';
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
FolkGeometry.register();
FolkXanadu.register();
// class AnimatedXanaduLink extends FolkXanadu {
// sourceAnimation = null;
// targetAnimation = null;
// observeSource() {
// super.observeSource();
// this.sourceAnimation = this.animate(
// [
// { opacity: 0.3, offset: 0 },
// { opacity: 1, offset: 0.1 },
// { opacity: 1, offset: 0.9 },
// { opacity: 0.3, offset: 1 },
// ],
// {
// timeline: new ViewTimeline({
// subject: this.sourceElement,
// }),
// rangeStart: 'entry 0%',
// rangeEnd: 'exit 100%',
// }
// );
// }
// unobserveSource() {
// super.unobserveSource();
// this.sourceAnimation?.cancel();
// this.sourceAnimation = null;
// }
// observeTarget() {
// super.observeTarget();
// this.targetAnimation = this.animate(
// [
// { opacity: 0.3, offset: 0 },
// { opacity: 1, offset: 0.1 },
// { opacity: 1, offset: 0.9 },
// { opacity: 0.3, offset: 1 },
// ],
// {
// timeline: new ViewTimeline({
// subject: this.targetElement,
// }),
// rangeStart: 'entry 0%',
// rangeEnd: 'exit 100%',
// }
// );
// }
// unobserveTarget() {
// super.unobserveTarget();
// this.targetAnimation?.cancel();
// this.targetAnimation = null;
// }
// }
// AnimatedXanaduLink.register();
</script>
</body>
</html>

View File

@ -94,18 +94,8 @@ function getPointsOnBezierCurveWithSplitting(
const red = lerp(r1, r2, t);
getPointsOnBezierCurveWithSplitting(
[p1, q1, r1, red],
0,
tolerance,
outPoints
);
getPointsOnBezierCurveWithSplitting(
[red, r2, q3, p4],
0,
tolerance,
outPoints
);
getPointsOnBezierCurveWithSplitting([p1, q1, r1, red], 0, tolerance, outPoints);
getPointsOnBezierCurveWithSplitting([red, r2, q3, p4], 0, tolerance, outPoints);
}
return outPoints;
}
@ -152,11 +142,7 @@ export function simplifyPoints(
return outPoints;
}
export function pointsOnBezierCurves(
points: readonly Point[],
tolerance: number = 0.15,
distance?: number
): Point[] {
export function pointsOnBezierCurves(points: readonly Point[], tolerance: number = 0.15, distance?: number): Point[] {
const newPoints: Point[] = [];
const numSegments = (points.length - 1) / 3;
for (let i = 0; i < numSegments; i++) {
@ -189,3 +175,9 @@ export function getSvgPathFromStroke(stroke: number[][]): string {
d.push('Z');
return d.join(' ');
}
export function verticesToPolygon(vertices: Vertex[]): string {
if (vertices.length === 0) return '';
return `polygon(${vertices.map((vertex) => `${vertex.x}px ${vertex.y}px`).join(', ')})`;
}

101
src/folk-xanadu.ts Normal file
View File

@ -0,0 +1,101 @@
import { AbstractArrow } from './arrows/abstract-arrow.js';
import { Vertex, verticesToPolygon } from './arrows/utils.js';
export class FolkXanadu extends AbstractArrow {
static tagName = 'folk-xanadu';
render(sourceRect: DOMRectReadOnly, targetRect: DOMRectReadOnly): void {
if (this.sourceElement === null || this.targetElement === null) {
this.style.clipPath = '';
return;
}
// If the right side of the target is to the left of the right side of the source then swap them
if (sourceRect.x + sourceRect.width > targetRect.x + targetRect.width) {
const temp = sourceRect;
sourceRect = targetRect;
targetRect = temp;
}
let sourceVertices = computeInlineVertices(Array.from(this.sourceElement.getClientRects()));
const targetVertices = computeInlineVertices(Array.from(this.targetElement.getClientRects()));
if (sourceVertices.length === 0 || targetVertices.length === 0) {
this.style.clipPath = '';
return;
}
// To trace the link we need to rotate the vertices of the source to start on the bottom right corner.
const maxRightCoordinate = Math.max.apply(
null,
sourceVertices.map((vertex) => vertex.x)
);
const maxBottomCoordinate = Math.max.apply(
null,
sourceVertices.filter((vertex) => vertex.x === maxRightCoordinate).map((vertex) => vertex.y)
);
const index = sourceVertices.findIndex(
(vertex) => vertex.x === maxRightCoordinate && vertex.y === maxBottomCoordinate
);
sourceVertices = sourceVertices.slice(index).concat(sourceVertices.slice(0, index));
this.style.clipPath = verticesToPolygon(sourceVertices.concat(targetVertices));
}
}
// The order that vertices are returned is significant
function computeInlineVertices(rects: DOMRect[]): Vertex[] {
rects = rects.map((rect) =>
DOMRectReadOnly.fromRect({
height: Math.round(rect.height),
width: Math.round(rect.width),
x: Math.round(rect.x),
y: Math.round(rect.y),
})
);
if (rects.length === 0) return [];
else if (rects.length === 1) {
const rect = rects[0];
return [
{ x: rect.left, y: rect.top },
{ x: rect.right, y: rect.top },
{ x: rect.right, y: rect.bottom },
{ x: rect.left, y: rect.bottom },
];
}
const vertices: Vertex[] = [];
if (rects[1].left < rects[0].left) {
vertices.push({ x: rects[1].left, y: rects[1].top }, { x: rects[0].left, y: rects[0].bottom });
}
vertices.push({ x: rects[0].left, y: rects[0].top }, { x: rects[0].right, y: rects[0].top });
const maxRightCoordinate = Math.max.apply(
null,
rects.map((rect) => rect.right)
);
const maxBottomCoordinate = Math.max.apply(
null,
rects.filter((rect) => rect.right === maxRightCoordinate).map((rect) => rect.bottom)
);
vertices.push({
x: maxRightCoordinate,
y: maxBottomCoordinate,
});
const lastRect = rects.at(-1)!;
if (lastRect.bottom > maxBottomCoordinate) {
vertices.push({ x: lastRect.right, y: lastRect.top }, { x: lastRect.right, y: lastRect.bottom });
}
vertices.push({ x: lastRect.left, y: lastRect.bottom });
return vertices;
}