<div class="controls">
<button class="resize-handle">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 19.5l-15-15m0 0v11.25m0-11.25h11.25" />
</svg>
</button>
<button class="resize-handle">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5l15 15m0 0V8.25m0 11.25H8.25" />
</svg>
</button>
</div>
<div class="container">
<img src="https://picsum.photos/1080/1080?random=12" alt="">
</div>
@layer demo {
.container {
position: absolute;
inset: anchor(--handle-1 top) anchor(--handle-2 right)
anchor(--handle-2 bottom) anchor(--handle-1 left);
}
img {
height: 100%;
width: 100%;
object-fit: cover;
pointer-events: none;
user-select: none;
}
.controls {
position: relative;
}
.resize-handle {
height: 48px;
aspect-ratio: 1;
background: hsl(0 0% 100% / 0.75);
position: absolute;
z-index: 2;
opacity: 0.5;
transition: opacity 0.2s, background 0.2s;
}
.resize-handle:is(:hover, :active) {
opacity: 1;
background: hsl(0 0% 100% / 1);
}
.resize-handle:first-of-type {
anchor-name: --handle-1;
left: calc(50% - 100px);
top: calc(50% - 100px);
}
.resize-handle:last-of-type {
anchor-name: --handle-2;
left: calc(50% + 100px);
top: calc(50% + 100px);
}
svg {
stroke-width: 3;
}
}
* {
box-sizing: border-box;
}
:root {
}
:where(html) {
background-color: transparent;
color-scheme: none;
}
body {
min-height: 100vh;
height: 100vh;
overflow: hidden;
display: grid;
place-items: center;
position: relative;
background-color: var(--surface-2);
}
import gsap from "https://cdn.skypack.dev/gsap@3.11.4";
import Draggable from "https://cdn.skypack.dev/gsap@3.11.4/Draggable";
gsap.registerPlugin(Draggable);
const IMG = document.querySelector("img");
const HANDLES = document.querySelectorAll(".resize-handle");
const createHandle = (el, limiter) => {
Draggable.create(el, {
type: "left, top",
allowContextMenu: true,
onDragStart: limiter
});
};
createHandle(HANDLES[0], function () {
const BOUNDS = IMG.getBoundingClientRect();
this.applyBounds({
top: window.innerHeight * -0.5,
left: window.innerWidth * -0.5,
width:
window.innerWidth * 0.5 + BOUNDS.right - window.innerWidth * 0.5 - 48,
height:
window.innerHeight * 0.5 + BOUNDS.bottom - window.innerHeight * 0.5 - 48
});
});
createHandle(HANDLES[1], function () {
const BOUNDS = IMG.getBoundingClientRect();
this.applyBounds({
top: 0 - (window.innerHeight * 0.5 - BOUNDS.top) + 48,
left: 0 - (window.innerWidth * 0.5 - BOUNDS.left) + 48,
width: window.innerWidth * 0.5 + (window.innerWidth * 0.5 - BOUNDS.left),
height: window.innerHeight * 0.5 + (window.innerHeight * 0.5 - BOUNDS.top)
});
});