Search code examples
cssreactjstailwind-cssradix-ui

How can I animate this tooltip for the radix-ui/react-slider component in React using Tailwind CSS?


I want to create a follow effect. currently the when i move the slider.thumb the tool tip moves instantly with the slider.thumb. but i want when to create a pulling effect with slight delay.

🎞️ Here is the Video Showing Desired Effect 🔗

this is my current code. & stackblitz link

import * as Slider from '@radix-ui/react-slider'
import { useState } from 'react'

export const InputRange = () => {
    const [value, setValue] = useState(2)

    return (
        <form>
            <Slider.Root
                className="relative flex items-center select-none touch-none w-full h-5 "
                defaultValue={[value]}
                min={1}
                max={30}
                step={1}
                aria-label="Volume"
                onValueChange={(e) => setValue(e[0])}
            >
                <Slider.Track className="bg-slate-200 relative grow rounded-full h-[3px]">
                    <Slider.Range className="absolute bg-black rounded-full h-full" />
                </Slider.Track>
                <Slider.Thumb className="relative group duration-150 block w-6 h-6 bg-black rounded-full active:scale-125 outline-none border-none cursor-pointer " >
                    <h1 className='absolute ease-in-out duration-150 opacity-0 group-active:opacity-100 group-active:-translate-y-12 -translate-x-8 -translate-y-6  px-5 py-2 bg-black text-center rounded-full text-white whitespace-nowrap text-xs font-bold'>{value} {value === 1 ? "night" : "nights"} </h1>
                </Slider.Thumb>
            </Slider.Root>
        </form>
    )
}

I tried to update the position of the tooltip for the radix-ui/react-slider component, but it caused the tooltip to move off the screen when I moved the slider.

const updateTooltipPosition = () => {
        const thumb = thumbRef.current.getBoundingClientRect()
        const tooltip = tooltipRef.current.getBoundingClientRect()
        // console.log(thumb.x );
        const tooltipCenter = tooltip.left + tooltip.width / 2
        console.log(tooltip.left , tooltipCenter)

        tooltipRef.current.style.transform = `translate(${}px, -120%)`
    }

tried using useEffect but did nothing.

  const [value, setValue] = useState(2)
  const tooltipRef = useRef(null)

  useEffect(() => {
    if (tooltipRef.current) {
      const tooltipWidth = tooltipRef.current.offsetWidth
      const thumbPosition = ((value - 1) / 29) * 100 // calculate the position of the thumb
      const tooltipPosition = thumbPosition - tooltipWidth / 2 // adjust for the width of the tooltip
      tooltipRef.current.style.left = `${tooltipPosition}%` // set the left position of the tooltip
    }
  }, [value])

other possible ways i wanted to try like using a library like react-spring, framer motion but don't know how it will solve the issue.


Solution

  • export const InputRange = () => {
      const [value, setValue] = useState(2);
      // Positioning offset state. Default value is when `value = 2`.
      const [offset, setOffset] = useState(14.8812);
      // Tooltip element reference.
      const ref = useRef(null);
    
      useLayoutEffect(() => {
        // Get the left and right edge coordinates of the tooltip element.
        const { left, right } = ref.current.getBoundingClientRect();
        // If left or right is positive, the tooltip element (or a parent)
        // does not have `display: none`:
        if (left + right > 0) {
          // Get the box of the form, that we'll use as our layout "edge".
          const containerRect = ref.current.closest('form').getBoundingClientRect();
    
          // Adjust the left and right sides to account for the 
          // `active:scale-125` "extra". `12` is thumb width ÷ 2.
          const limitLeft = containerRect.left - 12 * 0.25;
          const limitRight = containerRect.right + 12 * 0.25;
    
          // Get the overhang distance of the tooltip from its left and 
          // right edges, relative to the container's (adjusted) left and 
          // right edges, compensating for existing offset and the
          // `active:scale-125`.
          const offsetLeft = offset + (limitLeft - left) / 1.25;
          const offsetRight = offset + (limitRight - right) / 1.25;
          // If there is left overhang, set the offset.
          if (offsetLeft > 0) {
            setOffset(offsetLeft);
          // If there is right overhang, set the offset.
          } else if (offsetRight < 0) {
            setOffset(offsetRight);
          }
        }
      });
    
      return (
        <form>
          {/* … */}
          <div style={{ transform: `translateX(${offset}px)` }}>
            <h1 className="… -translate-x-1/2 left-1/2 …" ref={ref}>
              {value} {value === 1 ? 'time' : 'times'}{' '}
            </h1>
          </div>
          {/* … */}
        </form>
      );
    };
    

    Live example on Stackblitz