Angle Dial <mv-angle-dial>

读法与 CSS 一致的角度输入(0° 朝上,顺时针):旋钮位于一圈刻度之中,刻度同时充当吸附网格,旋钮后方的刻度被点亮,并随距离渐隐。

工作原理

读法与 CSS 一致的角度输入(0° 朝上,顺时针):旋钮位于一圈刻度之中,刻度同时充当吸附网格,旋钮后方的刻度被点亮,并随距离渐隐。拖动时带有柔和的 45° 定位档,或按住 Shift 吸附;完整的滑块键盘操作,支持表单关联。

分类表单
类型Web Component(<mv-angle-dial>)
状态稳定版
Keywordsangle, dial, knob, rotation, gradient, inspector, slider, form-associated, editor, ticks

When to use

  • A design tool lets users set a rotation or gradient direction in degrees by dragging around a dial
  • An inspector panel needs a compact angle control that snaps to 15 or 45 degree steps
  • A settings form must submit a direction or heading as a plain number of degrees

Avoid when

  • The value is a generic linear number rather than an angle → use Slider instead
  • Users need to type the exact angle more often than drag it → use Scrub Field instead
  • The audience is non-technical and a circular control would be unfamiliar in a regular form

安装

node scripts/add.mjs angle-dial --out ./src/marvelous

使用 Marvelous UI MCP 服务器的 AI 智能体:install_components({ slugs: ["angle-dial"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。

复制的文件(含依赖):tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/form.js, components/angle-dial/angle-dial.js, components/angle-dial/angle-dial.css。

用法

标准标记,可在此基础上通过属性、data-* 和 CSS 变量进行定制:

<div id="angle-demo">
  <style>
    #angle-demo { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; justify-content: center; width: 100%; }
    #angle-demo .ad-stage { display: grid; place-items: center; width: 15rem; height: 15rem; border-radius: var(--mv-radius-xl); 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); border: 1px solid var(--mv-border); }
    #angle-demo .ad-shape { width: 8rem; height: 8rem; border-radius: 1.5rem; box-shadow: 0 18px 40px -16px oklch(0.4 0.2 260 / .55); }
    #angle-demo .ad-panel { display: grid; gap: .6rem; width: 16.5rem; padding: .85rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); box-shadow: var(--mv-shadow-sm); }
    #angle-demo .ad-head { margin: 0; font: 600 .7rem var(--mv-font-sans); letter-spacing: .06em; text-transform: uppercase; color: var(--mv-fg-subtle); }
    #angle-demo .ad-row { display: grid; grid-template-columns: 4.75rem auto 1fr; align-items: center; gap: .6rem; font-size: .75rem; color: var(--mv-fg-muted); }
    #angle-demo .ad-row mv-scrub-field { width: 100%; }
    #angle-demo .ad-big { --mv-angle-dial-size: 3.5rem; }
    #angle-demo .ad-hint { margin: .15rem 0 0; font-size: .7rem; line-height: 1.5; color: var(--mv-fg-muted); }
    #angle-demo kbd { font: 500 .65rem var(--mv-font-mono); padding: 0 .25rem; border-radius: 3px; background: var(--mv-bg-muted); border: 1px solid var(--mv-border); }
  </style>
  <div class="ad-stage"><div class="ad-shape" id="angle-demo-shape"></div></div>
  <div class="ad-panel" role="group" aria-label="Inspector">
    <p class="ad-head">Transform</p>
    <div class="ad-row">
      <span id="angle-demo-l1">Rotation</span>
      <mv-angle-dial id="angle-demo-rot" name="rotation" value="-15" aria-labelledby="angle-demo-l1"></mv-angle-dial>
      <mv-scrub-field id="angle-demo-rot-field" aria-label="Rotation in degrees" unit="°" value="345" min="0" max="360" wrap precision="0"></mv-scrub-field>
    </div>
    <p class="ad-head">Fill</p>
    <div class="ad-row">
      <span id="angle-demo-l2">Gradient</span>
      <mv-angle-dial id="angle-demo-grad" class="ad-big" name="angle" value="135" snap="45" readout aria-labelledby="angle-demo-l2"></mv-angle-dial>
      <span></span>
    </div>
    <p class="ad-hint">Drag the dial or use the arrow keys. <kbd>Shift</kbd> snaps to the ticks, <kbd>Alt</kbd> drags freely, double-click resets.</p>
  </div>
</div>
<script type="module">
  const $ = (id) => document.getElementById(`angle-demo-${id}`);
  const apply = () => {
    $("shape").style.rotate = `${$("rot").value}deg`;
    $("shape").style.background = `linear-gradient(${$("grad").value}deg, oklch(0.8 0.13 190), oklch(0.6 0.2 265) 55%, oklch(0.68 0.2 340))`;
  };
  $("rot").addEventListener("mv-input", (e) => { $("rot-field").value = e.detail.value; apply(); });
  $("rot-field").addEventListener("mv-input", (e) => { $("rot").value = e.detail.value; apply(); });
  $("grad").addEventListener("mv-input", apply);
  Promise.all(["mv-angle-dial", "mv-scrub-field"].map((t) => customElements.whenDefined(t))).then(() => requestAnimationFrame(apply));
</script>

API

Attributes

Name类型DefaultDescription
valuenumber0Angle in degrees, normalized to [0, 360). Restored on form reset and on double-click.
namestringForm field name; the form receives the number of degrees.
labelstringAngleAccessible name. aria-labelledby / aria-label on the element or an external <label> work too.
stepnumber1Arrow-key step (Alt: a tenth of it, when precision allows).
snapnumber15Snap grid: Shift while dragging or with the arrows, PageUp/PageDown step, and the spacing of the ticks.
precisionnumber0Decimal places kept.
readoutbooleanShows the value in the center of the knob (for sizes of about 3rem and up).
localestringNumber formatting locale (defaults to the page lang, then en-US).
disabled / readonly / requiredbooleanStandard form states.

Properties

Name类型Description
valuenumberCurrent angle.
valueAsNumbernumberSame as value.
defaultValuenumberValue restored on form reset and double-click (the initial value).
stringsPartial<Record<string, string>>Overrides for every text: label ("Angle"), valueText ("{value} degrees"). English defaults.

Methods

NameDescription
focus()Focuses the slider.

Events

NameDescription
mv-inputWhile dragging and on every key press. detail: { value }.
mv-changeOn release (if the value changed) and on every key press. detail: { value }.

CSS classes

NameDescription
mv-angle-dial-face / -ticks / -tick / -arc / -plate / -needle / -knob / -readout / -badgeGenerated parts. -tick[data-major] marks every 90°.

CSS variables

NameDefaultDescription
--mv-angle-dial-sizevar(--mv-control-sm)Diameter; ticks, knob and readout scale with it.
--mv-angle-dial-bgBackground of the tick band.

Accessibility

The face has role="slider" with aria-valuemin 0, aria-valuemax 359 (the last value before it wraps, finer with precision), aria-valuenow and aria-valuetext “135 degrees” (localizable). Keyboard: →/↑ and ←/↓ by step (wrapping past 0°), Alt+arrow for a tenth of a step, Shift+arrow to the next snap tick, PageUp/PageDown ±snap, Home 0°, End the maximum. Esc while dragging restores the starting angle. Named by label, aria-labelledby/aria-label on the element, or a <label>. Visible focus ring; forced colors use system colors (Highlight for the value); the value bubble briefly shows after a key press; motion is limited to short transitions.

本页面由 AI 翻译。报告翻译问题