Search code examples
asp.netrazorblazormauimaui-blazor

pinch to zoom with pan gesture in maui blazor application


i have a image and when ever i pinch to zoom it, i should be able to move the image with fingers(pan gesture) with the same level of zoomed.

i tried some mouseoverevents and im able to do separately pinch to zoom and pan gesture,but together am facing some issues in maui blazor application.please resolve it. i have tried the following code

using Xamarin.Forms;

namespace YourNamespace
{
    public class ZoomPanContentView : ContentView
    {
        private double startScale = 1;
        private double currentScale = 1;
        private double xOffset = 0;
        private double yOffset = 0;

        public ZoomPanContentView()
        {
            var pinchGesture = new PinchGestureRecognizer();
            pinchGesture.PinchUpdated += OnPinchUpdated;
            GestureRecognizers.Add(pinchGesture);

            var panGesture = new PanGestureRecognizer();
            panGesture.PanUpdated += OnPanUpdated;
            GestureRecognizers.Add(panGesture);
        }

        private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
        {
            if (e.Status == GestureStatus.Started)
            {
                startScale = Content.Scale;
                Content.AnchorX = 0;
                Content.AnchorY = 0;
            }
            else if (e.Status == GestureStatus.Running)
            {
                currentScale += (e.Scale - 1) * startScale;
                currentScale = Math.Max(1, currentScale);
                currentScale = Math.Min(currentScale, 5);

                var renderedX = Content.X + xOffset;
                var deltaX = renderedX / Width;
                var deltaWidth = Width / (Content.Width * startScale);
                var originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;

                var renderedY = Content.Y + yOffset;
                var deltaY = renderedY / Height;
                var deltaHeight = Height / (Content.Height * startScale);
                var originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;

                var targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
                var targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);

                var transX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
                var transY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);

                Content.Scale = currentScale;
                Content.TranslateTo(transX, transY, 0);
            }
        }

        private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
        {
            if (currentScale == 1)
                return;

            switch (e.StatusType)
            {
                case GestureStatus.Running:
                    var xTrans = xOffset + e.TotalX;
                    var yTrans = yOffset + e.TotalY;
                    Content.TranslateTo(xTrans, yTrans, 0);
                    break;
                case GestureStatus.Completed:
                    xOffset = Content.TranslationX;
                    yOffset = Content.TranslationY;

                    var maxTransX = Math.Min(0, Content.Width * (currentScale - 1));
                    var maxTransY = Math.Min(0, Content.Height * (currentScale - 1));
                    xOffset = Math.Max(maxTransX, xOffset);
                    yOffset = Math.Max(maxTransY, yOffset);

                    var minTransX = Math.Max(Width - Content.Width * currentScale, 0);
                    var minTransY = Math.Max(Height - Content.Height * currentScale, 0);
                    xOffset = Math.Min(minTransX, xOffset);
                    yOffset = Math.Min(minTransY, yOffset);

                    Content.TranslateTo(xOffset, yOffset, 500, Easing.BounceOut);
                    break;
            }
        }
    }
}

Solution

  • The issue what you are facing can be resolved using the JS Hammer library. Below is the snippet for pinch-to-zoom with pan gestures on an image.

    Add the CDN

    <script src="https://cdn.jsdelivr.net/npm/hammerjs"></script>
    

    Add the JavaScript file:

    function hammerIt(elm) {
        var hammertime = new Hammer(elm, {});
        hammertime.get('pinch').set({ enable: true });
    
        var posX = 0,
            posY = 0,
            scale = 1,
            last_scale = 1,
            last_posX = 0,
            last_posY = 0,
            max_pos_x = 0,
            max_pos_y = 0,
            transform = "",
            el = elm;
    
        hammertime.on('doubletap pan pinch panend pinchend', function (ev) {
            if (ev.type == "doubletap") {
                transform = "translate3d(0, 0, 0) scale3d(2, 2, 1)";
                scale = 2;
                last_scale = 2;
    
                if (window.getComputedStyle(el, null).getPropertyValue('-webkit-transform').toString() != "matrix(1, 0, 0, 1, 0, 0)") {
                    transform = "translate3d(0, 0, 0) scale3d(1, 1, 1)";
                    scale = 1;
                    last_scale = 1;
                }
    
                el.style.webkitTransform = transform;
                transform = "";
            }
            
            // Pan
            if (scale != 1) {
                posX = last_posX + ev.deltaX;
                posY = last_posY + ev.deltaY;
                max_pos_x = Math.ceil((scale - 1) * el.clientWidth / 2);
                max_pos_y = Math.ceil((scale - 1) * el.clientHeight / 2);
    
                if (posX > max_pos_x) {
                    posX = max_pos_x;
                }
                if (posX < -max_pos_x) {
                    posX = -max_pos_x;
                }
                if (posY > max_pos_y) {
                    posY = max_pos_y;
                }
                if (posY < -max_pos_y) {
                    posY = -max_pos_y;
                }
            }
            if (ev.type == "pinch") {
                scale = Math.max(.999, Math.min(last_scale * ev.scale, 4));
            }
            if (ev.type == "pinchend") {
                last_scale = scale;
            }
    
            // Panend
            if (ev.type == "panend") {
                last_posX = posX < max_pos_x ? posX : max_pos_x;
                last_posY = posY < max_pos_y ? posY : max_pos_y;
            }
    
            if (scale != 1) {
                transform = "translate3d(" + posX + "px, " + posY + "px, 0) scale3d(" + scale + ", " + scale + ", 1)";
            }
    
            if (transform) {
                el.style.webkitTransform = transform;
            }
        });
    }
    

    And invoke it from your Razor page:

    @inject IJSRuntime JS;
    @page "/zoom-pan-pinch"
    <h3>Zoom</h3>
    
    <img id="myImage" @ref="imageRef" src="./Nature.jpg" alt="Image" />
     
     @code {
         private ElementReference imageRef;
     
         protected override async Task OnAfterRenderAsync(bool firstRender)
         {
             if (firstRender)
             {
                 await JS.InvokeVoidAsync("hammerIt", imageRef);
             }
         }
     }
    

    I have tested it and it worked fine for me. Please test and revert.