如何防止输入到跨度过渡动画顺风反应期间文本闪烁

问题描述 投票:0回答:1

我正在实现一个可扩展的输入字段,当失去焦点时该输入字段会转换为跨度。输入在焦点上扩展并在转换回跨度时缩小。虽然宽度动画工作正常,但在转换过程中我在文本内容中遇到了不需要的闪烁效果。

import React, { useState, useRef, useEffect } from 'react'

// Simple className merger function to replace cn()
const cn = (...classes: (string | undefined)[]) => {
  return classes.filter(Boolean).join(' ')
}

interface EditableProps {
  className?: string
  inputClassName?: string
  inlineTextClassName?: string
  placeholder?: string
  maxLength?: number
  isBold?: boolean
  onFocusChange?: (focused: boolean) => void
  inlineText?: string
}

export default function Editable({
  className,
  inputClassName,
  inlineTextClassName,
  placeholder = 'Enter your text here...',
  maxLength = 50,
  isBold = false,
  onFocusChange,
  inlineText = 'inline text'
}: EditableProps) {
  const [text, setText] = useState(placeholder)
  const [isFocused, setIsFocused] = useState(false)
  const inputRef = useRef<HTMLInputElement>(null)
  const containerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (inputRef.current && !inputRef.current.contains(event.target as Node)) {
        setIsFocused(false)
        onFocusChange?.(false)
      }
    }
    document.addEventListener('mousedown', handleClickOutside)
    return () => document.removeEventListener('mousedown', handleClickOutside)
  }, [onFocusChange])

  const handleFocus = () => {
    setIsFocused(true)
    onFocusChange?.(true)
    setTimeout(() => inputRef.current?.select(), 0)
  }

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setText(e.target.value.slice(0, maxLength))
  }

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') inputRef.current?.blur()
  }

  return (
    <div className={cn('flex items-center w-full', className)}>
      <div 
        ref={containerRef}
        className={cn(
          'transition-[flex] duration-300 ease-in-out',
          isFocused ? 'flex-1' : 'flex-initial'
        )}
      >
        {isFocused ? (
          <div className="flex items-center bg-gray-100 rounded-md w-full">
            <input
              ref={inputRef}
              value={text}
              onChange={handleChange}
              onBlur={() => setIsFocused(false)}
              onKeyDown={handleKeyDown}
              className={cn(
                'input-ghost focus:outline-none px-2 py-1 border-none bg-gray-100 rounded-md w-full',
                isBold ? 'font-bold' : 'font-normal',
                inputClassName
              )}
              style={{ fontWeight: isBold ? 'bold' : 'normal' }}
            />
            <div className="flex-shrink-0 px-2 text-sm text-gray-400">
              {text.length}/{maxLength}
            </div>
          </div>
        ) : (
          <span
            onClick={handleFocus}
            className={cn(
              'cursor-pointer hover:opacity-80 px-2 py-1 inline-block',
              isBold ? 'font-bold' : 'font-normal',
              inputClassName
            )}
          >
            {text}
          </span>
        )}
      </div>
      <span className={cn('ml-2 text-sm text-gray-500 flex-shrink-0', inlineTextClassName)}>{inlineText}</span>
    </div>
  )
}

enter image description here

当前行为:

  • 输入在焦点上扩展
  • 转换为跨度并在模糊时缩小
  • 宽度动画期间文本内容闪烁

预期行为:

  • 输入和跨度状态之间的平滑过渡
  • 动画期间没有文字闪烁

如何在输入到跨度过渡期间消除文本闪烁效果,同时保持平滑的宽度动画?

reactjs forms animation tailwind-css
1个回答
0
投票

闪烁的原因似乎是由于您正在转换的属性造成的。即,

flex-basis
,您可以在
auto
(隐式来自
flex-initial
)和
0%
(来自
flex-1
)之间切换。

相反,听起来您希望宽度停止/开始于

<span>
的宽度。如果是这样,请仅考虑过渡和更改
flex-grow

const { useState, useRef, useEffect } = React;

// Simple className merger function to replace cn()
const cn = (...classes) => {
  return classes.filter(Boolean).join(' ')
}

function Editable({
  className,
  inputClassName,
  inlineTextClassName,
  placeholder = 'Enter your text here...',
  maxLength = 50,
  isBold = false,
  onFocusChange,
  inlineText = 'inline text'
}) {
  const [text, setText] = useState(placeholder)
  const [isFocused, setIsFocused] = useState(false)
  const inputRef = useRef(null)
  const containerRef = useRef(null)

  useEffect(() => {
    const handleClickOutside = (event) => {
      if (inputRef.current && !inputRef.current.contains(event.target)) {
        setIsFocused(false)
        onFocusChange(false)
      }
    }
    document.addEventListener('mousedown', handleClickOutside)
    return () => document.removeEventListener('mousedown', handleClickOutside)
  }, [onFocusChange])

  const handleFocus = () => {
    setIsFocused(true)
    onFocusChange(true)
    setTimeout(() => inputRef.current.select(), 0)
  }

  const handleChange = (e) => {
    setText(e.target.value.slice(0, maxLength))
  }

  const handleKeyDown = (e) => {
    if (e.key === 'Enter') inputRef.current.blur()
  }

  return (
    <div className={cn('flex items-center w-full', className)}>
      <div 
        ref={containerRef}
        className={cn(
          'transition-[flex-grow] duration-300 ease-in-out',
          isFocused ? 'grow' : 'flex-initial'
        )}
      >
        {isFocused ? (
          <div className="flex items-center bg-gray-100 rounded-md w-full">
            <input
              ref={inputRef}
              value={text}
              onChange={handleChange}
              onBlur={() => setIsFocused(false)}
              onKeyDown={handleKeyDown}
              className={cn(
                'input-ghost focus:outline-none px-2 py-1 border-none bg-gray-100 rounded-md w-full',
                isBold ? 'font-bold' : 'font-normal',
                inputClassName
              )}
              style={{ fontWeight: isBold ? 'bold' : 'normal' }}
            />
            <div className="flex-shrink-0 px-2 text-sm text-gray-400">
              {text.length}/{maxLength}
            </div>
          </div>
        ) : (
          <span
            onClick={handleFocus}
            className={cn(
              'cursor-pointer hover:opacity-80 px-2 py-1 inline-block',
              isBold ? 'font-bold' : 'font-normal',
              inputClassName
            )}
          >
            {text}
          </span>
        )}
      </div>
      <span className={cn('ml-2 text-sm text-gray-500 flex-shrink-0', inlineTextClassName)}>{inlineText}</span>
    </div>
  )
}

ReactDOM.createRoot(document.getElementById('app')).render(<Editable onFocusChange={() => {}} />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js" integrity="sha512-QVs8Lo43F9lSuBykadDb0oSXDL/BbZ588urWVCRwSIoewQv/Ewg1f84mK3U790bZ0FfhFa1YSQUmIhG+pIRKeg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js" integrity="sha512-6a1107rTlA4gYpgHAqbwLAtxmWipBdJFcq8y5S/aTge3Bp+VAklABm2LO+Kg51vOWR9JMZq1Ovjl5tpluNpTeQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.4.15"></script>

<div id="app"></div>

© www.soinside.com 2019 - 2024. All rights reserved.