Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make image zoom in & out with mouse wheel in Blazor?

Tags:

c#

asp.net

blazor

I want to zoom in & out an image in blazor on asp.net.

As I use for Google Maps, I want to move the image position by zooming and dragging the image with the mouse wheel.(I want to use an image file, not a Google map.)

Example of use

Is there a way to zoom in, zoom out and drag specific images in blazor?

like image 692
philip Avatar asked Aug 28 '20 01:08

philip


People also ask

How can I make the zoom of a photo larger?

If you need a “larger zoom”, just change transform: scale (1.2) to however much is required. The above hover zoom works, but it may cover surrounding text when the image “expands out of place”. So this is an alternative with a “bounding box”:

How to zoom an image in a container using CSS?

Note that the image should zoom on hover inside the container element and do not come or flow outside of it when it gets zoomed. So, the basic idea is to limit the container element with the CSS overflow property. The zooming and animation parts will be handled with the CSS3 transform and transition properties respectively.

How do i Zoom in on a macro image?

Macro images and panoramas have to be zoomed in to view the tiny details that otherwise beat the eye. You can easily view such images by zooming in on them with Pixelied’s free online image zoomer tool via these simple steps. Upload the chosen image in PNG or JPG directly into the editor or drag-n-drop it to the editor.

Why do people zoom in on pictures?

Sometimes the picture is perfect but you need to zoom it in to put something or someone into focus or you simply want a larger image. Zooming often reduces the picture sharpness and a once beautiful picture becomes a blurry mess. You can easily avoid this with a free or paid photo editing tool, depending on your budget.


1 Answers

Note:

  • I would only use this on BlazorWasm and not BlazorServer because there might be quite a bit of lag if the network is slow.
  • It is probably easier to just use JavaScript and/or JavaScript interoperability (JS interop) but for this example I decided not to use JS Interop.

This component enables you to zoom by pressing shift while mouse wheel up (zoom out) or mouse wheel down (zoom in) and move the image by pressing mouse button 1 down while moving. (it's more panning than dragging it)

Restriction in Blazor: (at the time of writing this)

  • The biggest issue at the moment is not having access to the mouse OffsetX and OffsetY within the html element as described here and also here, so the moving of the image has to be done using CSS only.
  • The reason I used Shift for scrolling is because scrolling is not being blocked or disabled as described here even with @onscroll:stopPropagation, @onwheel:stopPropagation, @onmousewheel:stopPropagation and/or @onscroll:preventDefault, @onwheel:preventDefault, @onmousewheel:preventDefault set on the parent mainImageContainer element. The screen will still scroll left and right if the content is wider than the viewable page.

Solution:

The Zooming part is pretty straight forward, all you need to do is set the transform:scale(n); property in the @onmousewheel event.

The moving of the image is a bit more complex because there is no reference to where the mouse pointer is in relation to the image or element boundaries. (OffsetX and OffsetY)

The only thing we can determine is if a mouse button is pressed and then calculate what direction the mouse is moving in left, right, up or down.

The idea is then to move the position of element with the image by setting the top and left CSS values as percentages.

This component code:

@using System.Text;

<div id="mainImageContainer" style="display: block;width:@($"{ImageWidthInPx}px");height:@($"{ImageHeightInPx}px");overflow: hidden;">
    <div id="imageMover"
         @onmousewheel="MouseWheelZooming"
         style="@MoveImageStyle">
        <div id="imageContainer"
             @onmousemove="MouseMoving"
             style="@ZoomImageStyle">
            @*this div is used just for moving around when zoomed*@
        </div>
    </div>
</div>
@if (ShowResetButton)
{
    <div style="display:block">
        <button @onclick="ResetImgage">Reset</button>
    </div>
}

@code{

    /// <summary>
    /// The path or url of the image
    /// </summary>
    [Parameter]
    public string ImageUrlPath { get; set; }

    /// <summary>
    /// The width of the image
    /// </summary>
    [Parameter]
    public int ImageWidthInPx { get; set; }

    /// <summary>
    /// The height of the image
    /// </summary>
    [Parameter]
    public int ImageHeightInPx { get; set; }

    /// <summary>
    /// Set to true to show the reset button
    /// </summary>
    [Parameter]
    public bool ShowResetButton { get; set; }

    /// <summary>
    /// Set the amount the image is scaled by, default is 0.1f
    /// </summary>
    [Parameter]
    public double DefaultScaleBy { get; set; } = 0.1f;

    /// <summary>
    /// The Maximum the image can scale to, default = 5f
    /// </summary>
    [Parameter]
    public double ScaleToMaximum { get; set; } = 5f;

    /// <summary>
    /// Set the speed at which the image is moved by, default 2.
    /// 2 or 3 seems to work best.
    /// </summary>
    [Parameter]
    public double DefaultMoveBy { get; set; } = 2;

    //defaults
    double _CurrentScale = 1.0f;
    double _PositionLeft = 0;
    double _PositionTop = 0;
    double _OldClientX = 0;
    double _OldClientY = 0;
    double _DefaultMinPosition = 0;//to the top and left
    double _DefaultMaxPosition = 0;//to the right and down

    //the default settings used to display the image in the child div
    private Dictionary<string, string> _ImageContainerStyles;
    Dictionary<string, string> ImageContainerStyles
    {
        get
        {
            if (_ImageContainerStyles == null)
            {
                _ImageContainerStyles = new Dictionary<string, string>();
                _ImageContainerStyles.Add("width", "100%");
                _ImageContainerStyles.Add("height", "100%");
                _ImageContainerStyles.Add("position", "relative");
                _ImageContainerStyles.Add("background-size", "contain");
                _ImageContainerStyles.Add("background-repeat", "no-repeat");
                _ImageContainerStyles.Add("background-position", "50% 50%");
                _ImageContainerStyles.Add("background-image", $"URL({ImageUrlPath})");
            }
            return _ImageContainerStyles;
        }
    }

    private Dictionary<string, string> _MovingContainerStyles;
    Dictionary<string, string> MovingContainerStyles
    {
        get
        {
            if (_MovingContainerStyles == null)
            {
                InvokeAsync(ResetImgage);
            }
            return _MovingContainerStyles;
        }
    }

    protected async Task ResetImgage()
    {
        _PositionLeft = 0;
        _PositionTop = 0;
        _DefaultMinPosition = 0;
        _DefaultMaxPosition = 0;
        _CurrentScale = 1.0f;

        _MovingContainerStyles = new Dictionary<string, string>();
        _MovingContainerStyles.Add("width", "100%");
        _MovingContainerStyles.Add("height", "100%");
        _MovingContainerStyles.Add("position", "relative");
        _MovingContainerStyles.Add("left", $"{_PositionLeft}%");
        _MovingContainerStyles.TryAdd("top", $"{_PositionTop}%");
    
        await InvokeAsync(StateHasChanged);
    }

    string ZoomImageStyle { get => DictionaryToCss(ImageContainerStyles); }
    string MoveImageStyle { get => DictionaryToCss(MovingContainerStyles); }


    private string DictionaryToCss(Dictionary<string, string> styleDictionary)
    {
        StringBuilder sb = new StringBuilder();
        foreach (var kvp in styleDictionary.AsEnumerable())
        {
            sb.AppendFormat("{0}:{1};", kvp.Key, kvp.Value);
        }
        return sb.ToString();
    }


    protected async void MouseMoving(MouseEventArgs e)
    {
        //if the mouse button 1 is not down exit the function
        if (e.Buttons != 1)
        {
            _OldClientX = e.ClientX;
            _OldClientY = e.ClientY;
            return;
        }

        //get the % of the current scale to move by at least the default move speed plus any scaled changes
        //basically the bigger the image the faster it moves..
        double scaleFrac = (_CurrentScale / ScaleToMaximum);
        double scaleMove = (DefaultMoveBy * (DefaultMoveBy * scaleFrac));

        //moving mouse right
        if (_OldClientX < e.ClientX)
        {
            if ((_PositionLeft - DefaultMoveBy) <= _DefaultMaxPosition)
            {
                _PositionLeft += scaleMove;
            }
        }

        //moving mouse left
        if (_OldClientX > e.ClientX)
        {
            //if (_DefaultMinPosition < (_PositionLeft - DefaultMoveBy))
            if ((_PositionLeft + DefaultMoveBy) >= _DefaultMinPosition)
            {
                _PositionLeft -= scaleMove;
            }
        }

        //moving mouse down
        if (_OldClientY < e.ClientY)
        {
            //if ((_PositionTop + DefaultMoveBy) <= _DefaultMaxPosition)
            if ((_PositionTop - DefaultMoveBy) <= _DefaultMaxPosition)
            {
                _PositionTop += scaleMove;
            }
        }

        //moving mouse up
        if (_OldClientY > e.ClientY)
        {
            //if ((_PositionTop - DefaultMoveBy) > _DefaultMinPosition)
            if ((_PositionTop + DefaultMoveBy) >= _DefaultMinPosition)
            {
                _PositionTop -= scaleMove;
            }
        }

        _OldClientX = e.ClientX;
        _OldClientY = e.ClientY;

        await UpdateScaleAndPosition();
    }

    async Task<double> IncreaseScale()
    {
        return await Task.Run(() =>
        {
            //increase the scale first then calculate the max and min positions
            _CurrentScale += DefaultScaleBy;
            double scaleFrac = (_CurrentScale / ScaleToMaximum);
            double scaleDiff = (DefaultMoveBy + (DefaultMoveBy * scaleFrac));
            double scaleChange = DefaultMoveBy + scaleDiff;
            _DefaultMaxPosition += scaleChange;
            _DefaultMinPosition -= scaleChange;

            return _CurrentScale;
        });
    }

    async Task<double> DecreaseScale()
    {
        return await Task.Run(() =>
        {
            _CurrentScale -= DefaultScaleBy;
           
            double scaleFrac = (_CurrentScale / ScaleToMaximum);
            double scaleDiff = (DefaultMoveBy + (DefaultMoveBy * scaleFrac));
            double scaleChange = DefaultMoveBy + scaleDiff;
            _DefaultMaxPosition -= scaleChange;
            _DefaultMinPosition += scaleChange;//DefaultMoveBy;

            //fix descaling, move the image back into view when descaling (zoomin out)
            if (_CurrentScale <= 1)
            {
                _PositionLeft = 0;
                _PositionTop = 0;
            }
            else
            {
                //left can not be more than max position
                _PositionLeft = (_DefaultMaxPosition < _PositionLeft) ? _DefaultMaxPosition : _PositionLeft;

                //top can not be more than max position
                _PositionTop = (_DefaultMaxPosition < _PositionTop) ? _DefaultMaxPosition : _PositionTop;

                //left can not be less than min position
                _PositionLeft = (_DefaultMinPosition > _PositionLeft) ? _DefaultMinPosition : _PositionLeft;

                //top can not be less than min position
                _PositionTop = (_DefaultMinPosition > _PositionTop) ? _DefaultMinPosition : _PositionTop;
            }
            return _CurrentScale;
        });
    }

    protected async void MouseWheelZooming(WheelEventArgs e)
    {
        //holding shift stops the page from scrolling
        if (e.ShiftKey == true)
        {
            if (e.DeltaY > 0)
            {
                _CurrentScale = ((_CurrentScale + DefaultScaleBy) >= 5) ? _CurrentScale = 5f : await IncreaseScale();
            }
            if (e.DeltaY < 0)
            {
                _CurrentScale = ((_CurrentScale - DefaultScaleBy) <= 0) ? _CurrentScale = DefaultScaleBy : await DecreaseScale();
            }

            await UpdateScaleAndPosition();
        }
    }

    /// <summary>
    /// Refresh the values in the moving style dictionary that is used to position the image.
    /// </summary>    
    async Task UpdateScaleAndPosition()
    {
        await Task.Run(() =>
        {
            if (!MovingContainerStyles.TryAdd("transform", $"scale({_CurrentScale})"))
            {
                MovingContainerStyles["transform"] = $"scale({_CurrentScale})";
            }

            if (!MovingContainerStyles.TryAdd("left", $"{_PositionLeft}%"))
            {
                MovingContainerStyles["left"] = $"{_PositionLeft}%";
            }

            if (!MovingContainerStyles.TryAdd("top", $"{_PositionTop}%"))
            {
                MovingContainerStyles["top"] = $"{_PositionTop}%";
            }
        });
    }

}

This is the usage:

@page "/"
@using BlazorWasmApp.Components
Welcome to your new app.

<ZoomableImageComponent ImageUrlPath="images/Capricorn.png"
                        ImageWidthInPx=400
                        ImageHeightInPx=300
                        ShowResetButton=true
                        DefaultScaleBy=0.1f />

and this is the result:

enter image description here

I only tested this in chrome on a desktop computer without touch input.

like image 100
CobyC Avatar answered Oct 03 '22 05:10

CobyC