beta

Curve Editor <mv-curve-editor>

Editor de curvas de easing: arraste duas alças ou ajuste-as pelo teclado, com coordenadas ao vivo e overshoot para antecipação e quique. As famílias de curvas alternam entre in, out e in-out, um preset de mola é calculado como linear(), e uma pista mostra a prévia do movimento ao lado de uma referência linear. Integrado a formulários.

CategoriaFormulários
TipoWeb Component (<mv-curve-editor>)
Statusbeta
Keywordseasing, cubic-bezier, linear(), spring, curve, animation, inspector, form-associated, editor

When to use

  • A motion or animation tool lets users design a cubic-bezier or spring easing visually
  • An inspector needs easing presets by family and direction plus exact x1/y1/x2/y2 values with a live preview

Avoid when

  • The easing is a fixed design decision that end users never change; hardcode it instead
  • Users only pick among a few named easings → use Segmented instead

Instalação

node scripts/add.mjs curve-editor --out ./src/marvelous

Agente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["curve-editor"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Arquivos copiados (dependências incluídas): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/form.js, core/motion.js, components/curve-editor/curve-editor.js, components/curve-editor/curve-editor.css.

Uso

Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis CSS:

<div id="curve-demo">
  <style>
    #curve-demo { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; justify-content: center; width: 100%; }
    #curve-demo .cd-col { display: grid; gap: .6rem; width: 17rem; }
    #curve-demo .cd-stage { position: relative; height: 15rem; overflow: hidden; border-radius: var(--mv-radius-xl); border: 1px solid var(--mv-border); background: radial-gradient(circle at 1px 1px, color-mix(in oklch, var(--mv-fg) 12%, transparent) 1px, transparent 0) 0 0 / 12px 12px, var(--mv-bg); }
    #curve-demo .cd-toast { position: absolute; inset: auto 1rem 1rem; display: flex; align-items: center; gap: .7rem; padding: .7rem .8rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface-raised); box-shadow: var(--mv-shadow-lg); font-size: .8rem; }
    #curve-demo .cd-toast i { flex: none; width: 2rem; height: 2rem; border-radius: .6rem; background: linear-gradient(135deg, oklch(0.75 0.15 160), oklch(0.6 0.16 200)); }
    #curve-demo .cd-toast b { display: block; font-weight: 600; }
    #curve-demo .cd-toast small { color: var(--mv-fg-muted); }
    #curve-demo .cd-code { margin: 0; padding: .55rem .7rem; max-height: 3.2rem; overflow: hidden; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-md); background: var(--mv-bg-muted); color: var(--mv-fg-muted); font: 500 .66rem/1.5 var(--mv-font-mono); word-break: break-all; }
    #curve-demo .cd-panel { display: grid; gap: .6rem; width: 16.5rem; padding: .75rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); box-shadow: var(--mv-shadow-sm); }
    #curve-demo .cd-head { display: flex; justify-content: space-between; align-items: baseline; margin: 0; color: var(--mv-fg-subtle); font: 600 .7rem var(--mv-font-sans); letter-spacing: .06em; text-transform: uppercase; }
    #curve-demo .cd-dur { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: .6rem; font-size: .75rem; color: var(--mv-fg-muted); }
    #curve-demo .cd-dur output { font: 500 .7rem var(--mv-font-mono); font-variant-numeric: tabular-nums; color: var(--mv-fg); min-width: 3.4rem; text-align: end; }
  </style>
  <div class="cd-col">
    <div class="cd-stage">
      <div class="cd-toast" id="curve-demo-toast"><i></i><span><b>Export complete</b><small>final-cut.mp4 · 42 MB</small></span></div>
    </div>
    <p class="cd-code" id="curve-demo-out"></p>
  </div>
  <div class="cd-panel" role="group" aria-label="Inspector">
    <p class="cd-head">Toast enter</p>
    <label class="cd-dur"><span>Duration</span><input type="range" class="mv-slider" id="curve-demo-dur" min="200" max="2000" step="50" value="900"><output id="curve-demo-dur-out">900 ms</output></label>
    <mv-curve-editor id="curve-demo-editor" name="easing" label="Toast enter curve" value="cubic-bezier(0.34, 1.56, 0.64, 1)" duration="900"></mv-curve-editor>
  </div>
</div>
<script type="module">
  const $ = (id) => document.getElementById(`curve-demo-${id}`);
  const ed = $("editor");
  let timer = 0;
  let springMs = null;
  const show = () => { $("out").textContent = `transition-timing-function: ${ed.value};`; };
  const play = () => {
    clearTimeout(timer);
    if (!ed.isConnected) return;
    show();
    const ms = (!ed.points && springMs) || Number($("dur").value);
    $("toast").animate([{ translate: "0 140%", scale: 0.9, opacity: 0 }, { translate: "0 0", scale: 1, opacity: 1 }], { duration: ms, easing: ed.value, fill: "both" });
    timer = setTimeout(play, ms + 1400);
  };
  ed.addEventListener("mv-input", show);
  ed.addEventListener("mv-change", (e) => { springMs = e.detail.duration; play(); });
  $("dur").addEventListener("input", () => { $("dur-out").textContent = `${$("dur").value} ms`; ed.setAttribute("duration", $("dur").value); });
  $("dur").addEventListener("change", play);
  customElements.whenDefined("mv-curve-editor").then(() => requestAnimationFrame(play));
</script>

API

Attributes

NameTipoDefaultDescription
valuestringeasecubic-bezier(…), a keyword (ease, ease-in…) or linear(…). Restored on reset.
durationnumber1100Preview duration (ms); a spring uses its settling time.
y-min / y-maxnumber-0.4 / 1.4Vertical range of the plot: handles can go beyond 0..1 within this limit.
labelstringGroup name.
no-presets / no-previewbooleanHides the presets or the ball.
name / disabled / readonlyStandard behavior.

Properties

NameTipoDescription
valuestring“cubic-bezier(0.22, 1, 0.36, 1)” or “linear(…)”.
pointsnumber[] | null[x1, y1, x2, y2], null for a linear() curve.
stringsobjectDefault texts, for translation (label, start, end, handle, presets, direction, in, out, in-out, family names, x1…y2, points, settles). The label attribute wins. Assignable before the element is defined.

Methods

NameDescription
springEasing({ stiffness, damping, mass, points })Module export: damped spring (core spring()) sampled as linear(), with its duration.
parseEasing(str)Module export: parses cubic-bezier, keywords and linear().

Events

NameDescription
mv-inputWhile dragging or typing. detail: { value, points, preset, duration } (preset like "expo-out", "linear", "spring" or null; duration is the spring settling time in ms, else null).
mv-changeRelease, key press, preset, direction or committed field.

CSS classes

NameDescription
mv-curve-editor-plot / -handle / -tip / -path / -tangent / -preview / -ball / -ghost / -fields / -field / -readout / -presets / -dirs / -dir / -families / -presetGenerated parts.

CSS variables

NameDefaultDescription
--mv-curve-editor-width100%Width (the plot is square).

Accessibility

Named role="group". Each handle is a button with role="slider" (aria-roledescription “2D handle”) and an aria-valuetext like “x 0.42, y 0”; a coordinates badge shows the same values while it is focused, hovered or dragged. Arrow keys move by 0.01 (Shift 0.1, Alt 0.001), Page Up/Down move y by 0.1, Home/End set x to 0/1, and Escape cancels a drag. Fields are labelled native number inputs. Direction and family buttons are toggle buttons (aria-pressed). The preview is decorative (aria-hidden) and stays still with reduced motion; forced colors keep the curve, handles and pressed states visible.

Esta página foi traduzida com IA. Informar um problema de tradução