React-pdf: Keep point under mouse when zooming

Created on 28 Nov 2019  路  3Comments  路  Source: wojtekmaj/react-pdf

I'm attempting to clone Firefox's viewer.js zoom functionality and failing, but I am unable to tell if it's due to an error of my own or if scaling is calculated differently with this library.

Expected result:
While zooming, the point on the PDF my mouse is hovered over should stay under my mouse after being zoomed.
Expected

Actual result:
The page scrolls in a rather strange way.
Result
The further you scroll down the document, the more it fails to compensate.

I'm only using the scale property of the pages.

I'm using the same code that firefox uses for incrementing/decrementing zoom, as well as for calculating scroll compensation, but it just doesn't seem to be working out.

Here's what I have

For performing the actual scroll compensation:

    componentDidUpdate(prevProps, prevState, snapshot)
    {
        if (prevState.zoom !== this.state.zoom)
        {
            const viewer              = this.content.current
            let scaleCorrectionFactor = this.state.zoom / prevState.zoom - 1
            let rect                  = viewer.getBoundingClientRect()
            let dx                    = this.mouseX - rect.left  // mouseX/Y are mouse coordinates on screen
            let dy                    = this.mouseY - rect.top

            viewer.scrollLeft += dx * scaleCorrectionFactor
            viewer.scrollTop += dy * scaleCorrectionFactor
        }
    }

and to control the zoom (zoom out is the same, just obviously for reversing this):

    zoomIn(ticks)
    {
        this.setState(prevState => {
            let newZoom = prevState.zoom

            do
            {
                newZoom = (newZoom * DEFAULT_SCALE_DELTA).toFixed(2)
                newZoom = Math.ceil(newZoom * 10) / 10
                newZoom = Math.min(MAX_SCALE, newZoom)
            } while (--ticks > 0 && newZoom < MAX_SCALE)

            return {
                zoom: newZoom
            }
        })
    }

I've been at this for a while and all I can think of is that it has something to do with DPI or similar, I imagine.
Obvious answer is scaleCorrectionFactor is not correct and has failed to account for something.

question

Most helpful comment

I seem to have solved it, here is my solution for anyone attempting something similar.

getSnapshotBeforeUpdate(prevProps, prevState)
{
    return {x: this.content.current.scrollLeft, y: this.content.current.scrollTop}
}

componentDidUpdate(prevProps, prevState, snapshot)
{
    if (prevState.zoom !== this.state.zoom)
    {
        const viewer              = this.content.current
        let scaleCorrectionFactor = (this.state.zoom / prevState.zoom) - 1
        let rect                  = viewer.getBoundingClientRect()
        let dx                    = this.mouseX - rect.left
        let dy                    = this.mouseY - rect.top

        viewer.scrollLeft = snapshot.x * (this.state.zoom / prevState.zoom) + (dx * scaleCorrectionFactor)
        viewer.scrollTop = snapshot.y * (this.state.zoom / prevState.zoom) + (dy * scaleCorrectionFactor)
    }
}

I'll leave this open in case I forgot something and this doesn't cover every case.

All 3 comments

I seem to have solved it, here is my solution for anyone attempting something similar.

getSnapshotBeforeUpdate(prevProps, prevState)
{
    return {x: this.content.current.scrollLeft, y: this.content.current.scrollTop}
}

componentDidUpdate(prevProps, prevState, snapshot)
{
    if (prevState.zoom !== this.state.zoom)
    {
        const viewer              = this.content.current
        let scaleCorrectionFactor = (this.state.zoom / prevState.zoom) - 1
        let rect                  = viewer.getBoundingClientRect()
        let dx                    = this.mouseX - rect.left
        let dy                    = this.mouseY - rect.top

        viewer.scrollLeft = snapshot.x * (this.state.zoom / prevState.zoom) + (dx * scaleCorrectionFactor)
        viewer.scrollTop = snapshot.y * (this.state.zoom / prevState.zoom) + (dy * scaleCorrectionFactor)
    }
}

I'll leave this open in case I forgot something and this doesn't cover every case.

@zambony
Hey! just wondering how far you got into make that clone of mozilla's viewer.js ?
Thanks

@Carlosf96
I made a decent amount of progress, but it's unfortunately not very modular at the moment. It was made for a demo and I don't have the rights to distribute the code, otherwise I'd love to share it.
However if it's the zoom functionality you're after, I did make some changes to my answer above if you need that.

Was this page helpful?
0 / 5 - 0 ratings