我正在实现一个可扩展的输入字段,当失去焦点时该输入字段会转换为跨度。输入在焦点上扩展并在转换回跨度时缩小。虽然宽度动画工作正常,但在转换过程中我在文本内容中遇到了不需要的闪烁效果。
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>
)
}
当前行为:
预期行为:
如何在输入到跨度过渡期间消除文本闪烁效果,同时保持平滑的宽度动画?
闪烁的原因似乎是由于您正在转换的属性造成的。即,
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>