Skip to example

Value transform & path drawing

An example of linking SVG path length to a drag gesture, with Motion's drag(), mapValue() and svgEffect() functions.

JavaScript

Source code

<div class="container">
    <div class="icon-container">
        <svg class="progress-icon" viewBox="0 0 50 50">
            <path
                fill="none"
                stroke-width="2"
                d="M 0, 20 a 20, 20 0 1,0 40,0 a 20, 20 0 1,0 -40,0"
                transform="translate(5 5)"
            />
            <path
                class="tick"
                fill="none"
                stroke-width="2"
                d="M14,26 L 22,33 L 35,16"
            />
            <path
                class="cross-a"
                fill="none"
                stroke-width="2"
                d="M17,17 L33,33"
            />
            <path
                class="cross-b"
                fill="none"
                stroke-width="2"
                d="M33,17 L17,33"
            />
        </svg>
    </div>
</div>

<script type="module">
    import { drag, mapValue, motionValue, styleEffect, svgEffect } from "motion"

    const container = document.querySelector(".container")
    const box = document.querySelector(".icon-container")

    /**
     * Bind x to the box before calling drag(), so the drag gesture
     * moves this motion value.
     */
    const x = motionValue(0)
    styleEffect(box, { x })

    drag(box, {
        axis: "x",
        constraints: { left: 0, right: 0 },
        elastic: 0.5,
    })

    const xInput = [-100, 0, 100]

    styleEffect(container, {
        backgroundColor: mapValue(x, xInput, ["#ff4945", "#141210", "#52cd86"]),
    })

    svgEffect("path", {
        stroke: mapValue(x, xInput, ["#ff4945", "#0a0a0b", "#52cd86"]),
    })

    svgEffect(".tick", { pathLength: mapValue(x, [10, 100], [0, 1]) })
    svgEffect(".cross-a", { pathLength: mapValue(x, [-10, -55], [0, 1]) })
    svgEffect(".cross-b", { pathLength: mapValue(x, [-50, -100], [0, 1]) })
</script>

<style>
    .container {
        display: flex;
        justify-content: center;
        align-items: center;
        flex: 1;
        width: 480px;
        height: 300px;
        max-width: calc(100vw - 32px);
        border: 1px solid var(--border);
    }

    .icon-container {
        width: 100px;
        height: 100px;
        background-color: var(--foreground);
        padding: 20px;
        cursor: grab;
    }

    .icon-container:active {
        cursor: grabbing;
    }
</style>