Gestures
Arrange a room, at any zoom
Zoom the floor plan in or out, then drag the sofa, the table and the lamp about: each stays under your pointer, and inside the room.
Reduced motion: Nothing changes: a piece follows your pointer, and nothing moves by itself.
Why
A plan you can zoom is drawn with a CSS transform, so the pointer moves in the page's pixels while a piece moves in the plan's: at 140% a piece would run ahead of your pointer, and at 60% lag behind it. The pieces sit under a WardicConfig whose transformPagePoint is correctParentTransform(() => plan), which turns each point of a drag back through the plan's zoom, so a piece stays under your pointer at any zoom.
Each piece's dragConstraints is the plan itself. Its box is measured through the same mapping, so a piece stops at the room's walls, whatever the zoom.
The code
Motion's correctParentTransform takes a React ref to the plan; Wardic's takes the element, or an accessor of it.
import { correctParentTransform, drag, LazyWardic, w, WardicConfig } from "@wardic/solid"
import { createSignal, For } from "solid-js"
const PIECES = [
{ id: "sofa", name: "Sofa", x: 24, y: 24, width: 120, height: 52 },
{ id: "table", name: "Table", x: 64, y: 112, width: 72, height: 44 },
{ id: "lamp", name: "Lamp", x: 236, y: 32, width: 44, height: 44 },
] as const
/** The demo, with this page's `drag` beside the shell's Features (#526): a page without a drag ships none. */
export default function ArrangeRoom() {
return (
<LazyWardic features={[drag]}>
<ArrangeRoomDemo />
</LazyWardic>
)
}
function ArrangeRoomDemo() {
// The plan is zoomed with a CSS transform, so the pointer moves in the page's pixels while a
// piece moves in the plan's: at 140% a piece would run ahead of the pointer, at 60% lag behind.
const [zoom, setZoom] = createSignal(1)
let plan!: HTMLDivElement
return (
<div class="room-demo">
<label class="room-zoom">
<span>Zoom</span>
<input type="range" min="0.6" max="1.4" step="0.1" value={zoom()} onInput={(event) => setZoom(Number(event.currentTarget.value))} />
<output>{Math.round(zoom() * 100)}%</output>
</label>
<div class="room-frame">
<div class="room-plan" ref={plan} style={{ transform: `scale(${zoom()})` }}>
{/* correctParentTransform turns each pointer point back through the plan's zoom, so a piece
stays under the pointer at any zoom, and its constraints are measured in the plan's pixels. */}
<WardicConfig transformPagePoint={correctParentTransform(() => plan)}>
<For each={PIECES}>
{(piece) => (
<w.div
class="room-piece"
data-id={piece.id}
drag
dragConstraints={() => plan}
dragElastic={0.1}
dragMomentum={false}
whileDrag={{ scale: 1.05 }}
style={{ left: `${piece.x}px`, top: `${piece.y}px`, width: `${piece.width}px`, height: `${piece.height}px` }}
>
{piece.name}
</w.div>
)}
</For>
</WardicConfig>
</div>
</div>
</div>
)
}
"use client"
import { correctParentTransform, m, MotionConfig } from "motion/react"
import { useRef, useState } from "react"
const PIECES = [
{ id: "sofa", name: "Sofa", x: 24, y: 24, width: 120, height: 52 },
{ id: "table", name: "Table", x: 64, y: 112, width: 72, height: 44 },
{ id: "lamp", name: "Lamp", x: 236, y: 32, width: 44, height: 44 },
] as const
export default function ArrangeRoom() {
// The plan is zoomed with a CSS transform, so the pointer moves in the page's pixels while a
// piece moves in the plan's: at 140% a piece would run ahead of the pointer, at 60% lag behind.
const [zoom, setZoom] = useState(1)
const plan = useRef<HTMLDivElement>(null)
return (
<div className="room-demo">
<label className="room-zoom">
<span>Zoom</span>
<input type="range" min="0.6" max="1.4" step="0.1" value={zoom} onChange={(event) => setZoom(Number(event.currentTarget.value))} />
<output>{Math.round(zoom * 100)}%</output>
</label>
<div className="room-frame">
<div className="room-plan" ref={plan} style={{ transform: `scale(${zoom})` }}>
{/* correctParentTransform turns each pointer point back through the plan's zoom, so a piece
stays under the pointer at any zoom, and its constraints are measured in the plan's pixels. */}
<MotionConfig transformPagePoint={correctParentTransform(plan)}>
{PIECES.map((piece) => (
<m.div
key={piece.id}
className="room-piece"
data-id={piece.id}
drag
dragConstraints={plan}
dragElastic={0.1}
dragMomentum={false}
whileDrag={{ scale: 1.05 }}
style={{ left: piece.x, top: piece.y, width: piece.width, height: piece.height }}
>
{piece.name}
</m.div>
))}
</MotionConfig>
</div>
</div>
</div>
)
}