Skip to content

Anchor Example

Use autoupdate to reposition anchored content as its anchor moves.

Anchor Element

Anchored

Code

The following example assumes that you have imported the component and set up the theme.

Anchor
<div class="viewport">
<div class="anchor-element">
<p class="anchor-element-label">Anchor Element</p>
</div>
<sv-anchor>
<div class="anchored-element">
<p>Anchored</p>
</div>
</sv-anchor>
</div>
<script type="application/x-typescript" module>
const anchorEl = document.querySelector<HTMLDivElement>(".anchor-element")!;
const anchor = document.querySelector("sv-anchor")!;
anchor.anchorElement = anchorEl;
anchor.anchoredElement = document.querySelector(".anchored-element");
anchor.viewportElement = document.querySelector(".viewport");
anchor.preferredPositionOrder = [
["top", "center", "center"],
["left", "center", "center"],
["right", "center", "center"],
["bottom", "center", "center"],
];
anchor.autoupdate = true;
39 collapsed lines
// Drag implementation
let isDragging = false;
let startX = 0;
let startY = 0;
let currentTranslateX = 0;
let currentTranslateY = 0;
const onPointerDown = (event: PointerEvent) => {
isDragging = true;
startX = event.clientX;
startY = event.clientY;
anchorEl.setPointerCapture(event.pointerId);
};
const onPointerMove = (event: PointerEvent) => {
if (!isDragging) return;
const dx = event.clientX - startX;
const dy = event.clientY - startY;
const newTranslateX = currentTranslateX + dx;
const newTranslateY = currentTranslateY + dy;
anchorEl.style.transform = `translate(${newTranslateX}px, ${newTranslateY}px)`;
};
const onPointerUp = (event: PointerEvent) => {
isDragging = false;
const transform = anchorEl.style.transform.match(/translate\((.+?)px, (.+?)px\)/);
if (transform) {
currentTranslateX = parseFloat(transform[1]);
currentTranslateY = parseFloat(transform[2]);
}
anchorEl.releasePointerCapture(event.pointerId);
};
anchorEl.addEventListener("pointerdown", onPointerDown);
anchorEl.addEventListener("pointermove", onPointerMove);
anchorEl.addEventListener("pointerup", onPointerUp);
</script>
<style>
113 collapsed lines
.viewport {
overflow: hidden;
overflow-x: clip;
border: solid 1px red;
border-radius: 0.4em;
.anchor-element {
position: relative;
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
inline-size: 160px;
block-size: 160px;
margin: auto;
margin-block: 10em;
color: white;
text-align: center;
touch-action: none;
cursor: pointer;
border: solid 1px #aeae18;
border-radius: 1em;
&::after {
position: absolute;
inset-block-start: 50%;
inset-inline: -10% 0;
inline-size: 120%;
block-size: 1px;
content: "";
background-color: #aeae18;
}
&::before {
position: absolute;
inset-block: -10% 0;
inset-inline-start: 50%;
inline-size: 1px;
block-size: 120%;
content: "";
background-color: #aeae18;
}
}
.anchored-element {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
inline-size: 120px;
block-size: 120px;
color: white;
text-align: center;
background-color: red;
border-radius: 1em;
&::after {
content: "";
filter: invert();
}
p {
margin: 0;
}
sv-anchor:state(--top) & {
margin-block-end: 1em;
background-color: #ff5733;
&::after {
content: "Top";
}
}
sv-anchor:state(--bottom) & {
margin-block-start: 1em;
background-color: #1fba3b;
&::after {
content: "Bottom";
}
}
sv-anchor:state(--left) & {
margin-inline-end: 1em;
background-color: #3357ff;
&::after {
content: "Left";
}
}
sv-anchor:state(--right) & {
margin-inline-start: 1em;
background-color: #ff33a8;
&::after {
content: "Right";
}
}
sv-anchor:state(--misaligned) & {
display: none;
}
}
.anchor-element-label {
z-index: 2;
color: var(--sb-color-text);
pointer-events: none;
background-color: var(--sb-color-black);
}
}
</style>