javascript - onDrag-händelse clientX returnerar 0 i Firefox när man konverterar komponent till Typescript


Jag konverterar en React JS-anpassad slider-komponent till en React Typescript-komponent (jsx) och stöter på problemet att den slutar glida i Firefox. e.clientX är alltid 0, men den vanliga React JS-komponenten fungerar som förväntat i Firefox. De fungerar båda bra i Chrome.

Jag är inte säker på om detta fortfarande är den kända Firefox-buggen gällande "drag"-händelsen. Jag har läst dessa källor: 1, 2, 3, 4, 5.

Frågor:

  1. Är detta fortfarande samma bugg som nämns i några av de nämnda källorna?
  2. Varför händer detta specifikt i Typescript, och inte i Javascript?
  3. Vilken lösning skulle du föreslå för full webbläsarstöd (Firefox, Chrome, Safari)?

Javascript-version

import {useRef, useState} from "react";

export default function Slider({initialValue, onChange}) {
    const sliderEl = useRef(null)
    const [dragging, setDragging] = useState(false)
    const [value, setValue] = useState(initialValue)

    const calculateValue = (x) => {
        const {x: sliderX, width: sliderWidth} = sliderEl.current.getBoundingClientRect()
        const value = (x - sliderX) / sliderWidth

        return Math.max(0, Math.min(value, 1))
    }

    const handleDrag = (e) => {
        if (e.clientX === 0) {
            return
        }

        setValue(calculateValue(e.clientX))
    }

    const handleClick = (e) => {
        const value = calculateValue(e.clientX)
        setValue(value)
        onChange(value)
    }

    const offsetLeft = `calc(${value * 100}% - 0.5rem)`

    return <div className="relative h-4" onMouseDown={handleClick} ref={sliderEl}>
        <div className="bg-primary-5 rounded-full h-1 w-full top-1.5 absolute"/>
        <div className="bg-primary-500 rounded-l-full h-1 top-1.5 absolute" style={{width: `${value * 100}%`}}/>
        <div className="rounded-full bg-primary-700 w-4 h-4 absolute top-0" style={{left: offsetLeft}}/>
        <div className="rounded-full bg-primary-500 w-4 h-4 absolute top-0 cursor-pointer"
            draggable={true}
            style={{left: offsetLeft, opacity: dragging ? 0 : 1}}
            onDragStart={(e) => { setDragging(true) }}
            onDrag={handleDrag}
            onDragEnd={(e) => {
                setDragging(false)
                onChange && onChange(value)
            }}
    />
    </div>
}

Typescript-konvertering

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

type SliderProps = {
    initialValue: number,
    onChange: (value: number) => void
}

export default function Slider({ initialValue = 0, onChange }: SliderProps) {
    const sliderEl = useRef<HTMLDivElement>(null)
    const [dragging, setDragging] = useState(false)
    const [value, setValue] = useState<number>(initialValue)

    const calculateValue = (x: number): number => {
        if(!sliderEl.current) return 0
        const { x: sliderX, width: sliderWidth }: DOMRect = sliderEl.current.getBoundingClientRect()
        const value = (x - sliderX) / sliderWidth

        return Math.max(0, Math.min(value, 1))
    }

    const handleDrag = (e: React.MouseEvent<HTMLDivElement>) => {
        if (e.clientX === 0) {
            return
        }
        
        setValue(calculateValue(e.clientX))
    }

    const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
        const value = calculateValue(e.clientX)

        if(value) {
            setValue(value)
            onChange(value)
        }
    }

    const offsetLeft = `calc(${value * 100}% - 0.5rem)`

    return <div className="relative h-4" onMouseDown={handleClick} ref={sliderEl}>
        <div className="bg-primary-5 rounded-full h-1 w-full top-1.5 absolute"/>
        <div className="bg-primary-500 rounded-l-full h-1 top-1.5 absolute" style={{width: `${value * 100}%`}}/>
        <div className="rounded-full bg-primary-700 w-4 h-4 absolute top-0" style={{left: offsetLeft}}/>
        <div className="rounded-full bg-primary-500 w-4 h-4 absolute top-0 cursor-pointer"
            draggable={true}
            style={{left: offsetLeft, opacity: dragging ? 0 : 1}}
            onDragStart={(e) => { setDragging(true) }}
            onDrag={handleDrag}
            onDragEnd={(e) => {
                setDragging(false)
                onChange && onChange(value)
            }}
    />
    </div>
}

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

type SliderProps = {
    initialValue: number,
    onChange: (value: number) => void
}

export default function Slider({ initialValue = 0, onChange }: SliderProps) {
    const sliderEl = useRef<HTMLDivElement>(null)
    const [dragging, setDragging] = useState(false)
    const [value, setValue] = useState<number>(initialValue)

    const calculateValue = (x: number): number => {
        if(!sliderEl.current) return 0
        const { x: sliderX, width: sliderWidth }: DOMRect = sliderEl.current.getBoundingClientRect()
        const value = (x - sliderX) / sliderWidth

        return Math.max(0, Math.min(value, 1))
    }

    const handleDrag = (e: React.MouseEvent<HTMLDivElement>) => {
        e.preventDefault()
        console.log(e.clientX)
        if (e.clientX === 0) {
            return
        }
        
        setValue(calculateValue(e.clientX))
    }

    const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
        const value = calculateValue(e.clientX)

        if(value) {
            setValue(value)
            onChange(value)
        }
    }

    const offsetLeft = `calc(${value * 100}% - 0.5rem)`

    return <div className="relative h-4" onMouseDown={handleClick} ref={sliderEl}>
        <div className="bg-primary-5 rounded-full h-1 w-full top-1.5 absolute"/>
        <div className="bg-primary-500 rounded-l-full h-1 top-1.5 absolute" style={{width: `${value * 100}%`}}/>
        <div className="rounded-full bg-primary-700 w-4 h-4 absolute top-0" style={{left: offsetLeft}}/>
        <div className="rounded-full bg-primary-500 w-4 h-4 absolute top-0 cursor-pointer"
            draggable={true}
            style={{left: offsetLeft, opacity: dragging ? 0 : 1}}
            onDragStart={(e) => { setDragging(true) }}
            onDrag={handleDrag}
            onDragEnd={(e) => {
                setDragging(false)
                onChange && onChange(value)
            }}
    />
    </div>
}

Tack!