数字步进器

加一时数字向上滑入,减一时向下滑入,让变化的方向既能看到也能感受到。

  • CSS
  • 表单与控件
  • 点击
  • 收录于
人数
2

AI 提示词

用 React 和 CSS 制作一个两侧带减号和加号按钮的数量步进器。在 state 中保存数值和最近一次变化的方向。数字放在一个以数值为 key 的 span 中,让 React 在每次变化时重新挂载它,并设置 data-dir="up" 或 "down"。挂载时播放一个 280ms 的 keyframe,缓动 cubic-bezier(0.16, 1, 0.3, 1):向上时从 translateY(60%)、opacity 0 开始,向下时从 translateY(-60%) 开始,结束于 translateY(0)、opacity 1。数字框设置 overflow: hidden 和 tabular-nums。数值限制在 1 到 9 之间,到达上下限时禁用对应按钮。数字外层设置 aria-live="polite",播报新的数值。在 prefers-reduced-motion 下去掉滑动。标签为“人数”。

源代码

Demo.tsx
'use client'

import { useState } from 'react'
import s from './demo.module.css'

export default function NumberStepper({ labels }: { labels: Record<string, string> }) {
  const [state, setState] = useState({ n: 2, dir: 'up' })
  const step = (d: 1 | -1) => setState(({ n }) => ({ n: n + d, dir: d > 0 ? 'up' : 'down' }))

  return (
    <div className={s.wrap}>
      <span className={s.label}>{labels.label}</span>
      <div className={s.stepper}>
        <button type="button" className={s.btn} aria-label={labels.minus} disabled={state.n <= 1} onClick={() => step(-1)}>
          −
        </button>
        <span className={s.value} aria-live="polite">
          <span key={state.n} className={s.num} data-dir={state.dir}>
            {state.n}
          </span>
        </span>
        <button type="button" className={s.btn} aria-label={labels.plus} disabled={state.n >= 9} onClick={() => step(1)}>
          +
        </button>
      </div>
    </div>
  )
}
demo.module.css
.wrap {
  display: grid;
  justify-items: center;
  gap: 12px;
}

.label {
  font: 500 var(--text-sm) / 1 var(--font-body);
  color: var(--stage-muted);
}

.stepper {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--stage-rule);
  border-radius: 999px;
  background: var(--stage-surface);
}

.btn {
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--stage-ink);
  font: 400 var(--text-2xl) / 1 var(--font-body);
  cursor: pointer;
  transition: background-color var(--dur-micro);
}

.btn:hover:not(:disabled) { background: var(--stage-bg); }
.btn:disabled { color: var(--stage-rule); cursor: not-allowed; }
.btn:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

.value {
  display: grid;
  place-items: center;
  width: 48px;
  height: 44px;
  overflow: hidden;
}

.num {
  font: 600 var(--text-2xl) / 1 var(--font-display);
  font-variant-numeric: tabular-nums;
  color: var(--stage-ink);
  animation: in-up 280ms var(--ease-out);
}

.num[data-dir='down'] { animation-name: in-down; }

@keyframes in-up {
  from { transform: translateY(60%); opacity: 0; }
}

@keyframes in-down {
  from { transform: translateY(-60%); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .num { animation: none; }
}

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。