展开搜索框

圆形的搜索图标点击后伸展成完整的输入框,内容为空时离开又会收回去。

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

AI 提示词

用 React 和 CSS 制作可展开的搜索框。收起时是一个 44px 的圆形,内含搜索图标按钮和一个宽度为 0 的 input。点击图标后进入展开状态:容器宽度在 360ms 内从 44px 过渡到 240px,缓动 cubic-bezier(0.16, 1, 0.3, 1),input 淡入,并在过渡开始时把焦点移到 input 上。input 失焦且内容为空时,或按下 Escape 时,再次收起,并把焦点还给图标按钮。图标固定在左侧,避免跳动。按钮设置 aria-expanded 和 aria-controls。在 prefers-reduced-motion 下展开和收起不做宽度过渡。占位文字为“搜索动效…”。

源代码

Demo.tsx
'use client'

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

export default function ExpandingSearch({ labels }: { labels: Record<string, string> }) {
  const [open, setOpen] = useState(false)
  const input = useRef<HTMLInputElement>(null)
  const button = useRef<HTMLButtonElement>(null)
  const id = useId()

  const close = (refocus: boolean) => {
    setOpen(false)
    if (refocus) button.current?.focus()
  }

  return (
    <div className={s.search} data-open={open || undefined}>
      <button
        ref={button}
        type="button"
        className={s.icon}
        aria-label={labels.open}
        aria-expanded={open}
        aria-controls={id}
        onClick={() => {
          setOpen(true)
          input.current?.focus()
        }}
      >
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <circle cx="11" cy="11" r="6.5" />
          <path d="M16 16l4 4" />
        </svg>
      </button>
      <input
        ref={input}
        id={id}
        type="search"
        className={s.input}
        placeholder={labels.placeholder}
        tabIndex={open ? 0 : -1}
        onBlur={(e) => !e.currentTarget.value && close(false)}
        onKeyDown={(e) => e.key === 'Escape' && close(true)}
      />
    </div>
  )
}
demo.module.css
.search {
  display: flex;
  align-items: center;
  width: 44px;
  height: 44px;
  overflow: hidden;
  border: 1px solid var(--stage-rule);
  border-radius: 999px;
  background: var(--stage-surface);
  transition: width 360ms var(--ease-out), border-color var(--dur-short);
}

.search[data-open] { width: min(240px, 80vw); border-color: var(--stage-accent); }

.icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 42px;
  height: 42px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--stage-ink);
  cursor: pointer;
}

.icon:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -4px; }

.icon svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0 14px 0 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--stage-ink);
  font: 400 var(--text-sm) / 1 var(--font-body);
  opacity: 0;
  transition: opacity 200ms var(--ease-out);
}

.input::placeholder { color: var(--stage-muted); }
.search[data-open] .input { opacity: 1; transition-delay: 120ms; }

@media (prefers-reduced-motion: reduce) {
  .search, .input { transition: none; }
}

投稿创意

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

你想投稿的是?

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