Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a "onGrab" event to the Slider #3575

Open
gerrod opened this issue May 29, 2019 · 5 comments
Open

Add a "onGrab" event to the Slider #3575

gerrod opened this issue May 29, 2019 · 5 comments

Comments

@gerrod
Copy link

gerrod commented May 29, 2019

Environment

Package version: @blueprintjs/core 3.15.1
Browser: Chrome 74+

Feature request

Currently the slider supports an onChange and onRelease event - it would be useful to also have an onGrab (or similar) event which is invoked when the user grabs the slider handle without releasing it.

Examples

A scenario when this would be useful is when using the slider as a scrubber control for a video. If the value of the slider is updated due to the user grabbing the handle and sliding, we would want to:

  • Immediately reflect the new value in the slider so that it stays responsive; but
  • Debounce the updates that tell the video to load at the current time. Otherwise we end up loading/trying to show a lot of "intermediate" frames that just get dropped as the user slides the handle.
@adidahiya
Copy link
Contributor

Sorry, I don't see how this is different from onChange?

@gerrod
Copy link
Author

gerrod commented May 30, 2019

Hi @adidahiya!

Sorry, I don't see how this is different from onChange?

Hmm, well you could definitely just use onChange to achieve the same result IF the event itself told you whether or not the user was grabbing/dragging the handle.

Here's an example of how the onGrab event may be useful:

const PlaybackTimeSlider = React.memo((props: IProps) => {
    // A context object which contains the shared state about the current playback time.
    const { videoTimeMillis, setVideoTimeMillis } = useContext(PlaybackContext);

    // Has the user grabbed the time slider?
    const [isGrabbed, setIsGrabbed] = useState(false);
    const handleGrab = useCallback(() => setIsGrabbed(true), []);

    // This is the current value of the slider
    const [sliderTimeMillis, setSliderTimeMillis] = useState(videoTimeMillis);

    // If the user releases the handle, immediately push the updated time to the context.
    const handleRelease = useCallback(releasedValue => {
        setIsGrabbed(false);
        setVideoTimeMillis(releasedValue);
    }, [setVideoTimeMillis]);

    // If the time is updated externally, immediately reflect those changes on the slider
    useEffect(() => { setSliderTimeMillis(videoTimeMillis) }, []);

    useEffect(() => {
        if (isGrabbed) {
            // If the user has grabbed the handle, debounce updates to the video time while 
            // they're dragging it to prevent loading of frames that are just going to get dropped
            debounce(() => { setVideoTimeMillis(sliderTimeMillis) }, 400);

        } else {
            // Otherwise we can immediately push the new video time
            setVideoTimeMillis(sliderTimeMillis);
        }
    }, [isGrabbed, sliderTimeMillis]);

    return (
        <>
            <Slider
                value={sliderTimeMillis}
                onChange={setSliderTimeMillis}
                onGrab={handleGrab}
                onRelease={handleRelease}
            />

            {isPlaying && <PlayHead />}
        </>
    );
});

I hope that makes sense!

@iongion
Copy link

iongion commented Jan 15, 2020

@gerrod This would be very useful!
@adidahiya - Distinguishing change source would be very useful indeed. Change would have multiple phases:

  • change start - when drag handle is pressed down
  • change perform - when drag handle is being moved
  • change end - when drag handle is released

Very useful when controlling for example a video player. You need to detect change start to be able to pause playback otherwise the slider would tremble/wobble during scrubbing / seeking.
For API design, you could have as another detail of onChange or you could implement the onGrab, meaning change.start. Nevertheless, I always need to go implement my own slider due to this in most of the UI frameworks. Have a look at this API https://refreshless.com/nouislider/events-callbacks - although it is a dedicated project for a single component in the entire Blueprint framework, it shows how nice abstractions can help reuse in common cases.

@iongion
Copy link

iongion commented Jan 15, 2020

If it is of any use, I am currently doing this React ref hack to be able to have a start phase:

    const element = ReactDOM.findDOMNode(
      this.sliderRef.current
    ) as HTMLElement;
    element.addEventListener("mousedown", this.onSliderChangeStart);
    element.addEventListener("touchdown", this.onSliderChangeStart);

The handle bubbles so no need to intercept that. But there are of course inconveniences, posted here because it may be helpful for someone.

@adidahiya
Copy link
Contributor

@iongion thanks for the bump and the link to that detailed slider API, that's very useful. I'm not sure when I'll get around to working on this, but I'm open to PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants