reactjs - How to prevent text flicker during input-to-span transition animation tailwind react - Stack Overflow

I'm implementing an expandable input field that transforms into a span when losing focus. The inpu

I'm implementing an expandable input field that transforms into a span when losing focus. The input expands on focus and shrinks when converting back to a span. While the width animation works correctly, I'm experiencing an unwanted flickering effect in the text content during the transition.

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>
  )
}

Current Behavior:

  • Input expands on focus
  • Converts to span and shrinks on blur
  • Text content flickers during the width animation

Expected Behavior:

  • Smooth transition between input and span states
  • No text flickering during the animation

How can I eliminate the text flickering effect while maintaining the smooth width animation during the input-to-span transition?

I'm implementing an expandable input field that transforms into a span when losing focus. The input expands on focus and shrinks when converting back to a span. While the width animation works correctly, I'm experiencing an unwanted flickering effect in the text content during the transition.

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>
  )
}

Current Behavior:

  • Input expands on focus
  • Converts to span and shrinks on blur
  • Text content flickers during the width animation

Expected Behavior:

  • Smooth transition between input and span states
  • No text flickering during the animation

How can I eliminate the text flickering effect while maintaining the smooth width animation during the input-to-span transition?

Share Improve this question edited Nov 18, 2024 at 18:45 j08691 208k32 gold badges269 silver badges280 bronze badges asked Nov 18, 2024 at 13:32 Yehezkiel LYehezkiel L 4394 silver badges13 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

It seems the cause of the flicker is due to the properties you are transitioning. Namely, flex-basis, where you toggle between auto (implicitly from flex-initial) and 0% (from flex-1).

Instead, it sounds like you'd want to have the width stop/start at the width of the <span>. If so, consider transitioning and changing flex-grow only:

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/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/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/3.4.15"></script>

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

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745613848a4636111.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信