- Home /
Pinch and Zoom functionality on Canvas UI Images
I have a scroll view with the set of images.When I click on one of the images They enlarge to fit the full screen.Now i want to provide pinch,zoom and Pan functionality on every image.Just like google play store or android Gallery.
If you want to zoom in you can play with the Field Of View of the camera (if it's using perspective). Have a look at this post:
http://answers.unity3d.com/questions/218347/how-do-i-make-the-camera-zoom-in-and-out-with-the.html
@QuentinLG thank you for your suggestion actually i want to implement this functionality without camera.but i will take a look at that post and try to seek for solution
Why wouldn't you use the camera? You can use an orthographic camera, and just change the orthographic size and position, or a perspective one, and just change the position in all 3 dimensions for pan/zoom.
If you really don't want to use the camera, you could use the transform.localscale of the image to enlarge/shrink it, as if you were zoo$$anonymous$$g, then just alter the transform.position or transform.localPosition to simulate panning.
If you want something similar to gallery, there should be 2 different views: 1- for scrolling images (in loop or not) and 2 - for previewing a single image. Both could be ScrollViews. I remember doing something similar with a single image in a ScrollView, zoo$$anonymous$$g is simply changing scroll content scale and the pan is grant as default for scroll view :)
Answer by $$anonymous$$ · Nov 02, 2017 at 01:33 PM
here a solution
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PinchableScrollRect : ScrollRect
{
[SerializeField] float _minZoom = .1f;
[SerializeField] float _maxZoom = 10;
[SerializeField] float _zoomLerpSpeed = 10f;
float _currentZoom = 1;
bool _isPinching = false;
float _startPinchDist;
float _startPinchZoom;
Vector2 _startPinchCenterPosition;
Vector2 _startPinchScreenPosition;
float _mouseWheelSensitivity = 1;
bool blockPan = false;
protected override void Awake()
{
Input.multiTouchEnabled = true;
}
private void Update()
{
if (Input.touchCount == 2)
{
if (!_isPinching)
{
_isPinching = true;
OnPinchStart();
}
OnPinch();
}
else
{
_isPinching = false;
if (Input.touchCount == 0)
{
blockPan = false;
}
}
//pc input
float scrollWheelInput = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Abs(scrollWheelInput) > float.Epsilon)
{
_currentZoom *= 1 + scrollWheelInput * _mouseWheelSensitivity;
_currentZoom = Mathf.Clamp(_currentZoom, _minZoom, _maxZoom);
_startPinchScreenPosition = (Vector2)Input.mousePosition;
RectTransformUtility.ScreenPointToLocalPointInRectangle(content, _startPinchScreenPosition, null, out _startPinchCenterPosition);
Vector2 pivotPosition = new Vector3(content.pivot.x * content.rect.size.x, content.pivot.y * content.rect.size.y);
Vector2 posFromBottomLeft = pivotPosition + _startPinchCenterPosition;
SetPivot(content, new Vector2(posFromBottomLeft.x / content.rect.width, posFromBottomLeft.y / content.rect.height));
}
//pc input end
if (Mathf.Abs(content.localScale.x - _currentZoom) > 0.001f)
content.localScale = Vector3.Lerp(content.localScale, Vector3.one * _currentZoom, _zoomLerpSpeed * Time.deltaTime);
}
protected override void SetContentAnchoredPosition(Vector2 position)
{
if (_isPinching || blockPan) return;
base.SetContentAnchoredPosition(position);
}
void OnPinchStart()
{
Vector2 pos1 = Input.touches[0].position;
Vector2 pos2 = Input.touches[1].position;
_startPinchDist = Distance(pos1, pos2) * content.localScale.x;
_startPinchZoom = _currentZoom;
_startPinchScreenPosition = (pos1 + pos2) / 2;
RectTransformUtility.ScreenPointToLocalPointInRectangle(content, _startPinchScreenPosition, null, out _startPinchCenterPosition);
Vector2 pivotPosition = new Vector3(content.pivot.x * content.rect.size.x, content.pivot.y * content.rect.size.y);
Vector2 posFromBottomLeft = pivotPosition + _startPinchCenterPosition;
SetPivot(content, new Vector2(posFromBottomLeft.x / content.rect.width, posFromBottomLeft.y / content.rect.height));
blockPan = true;
}
void OnPinch()
{
float currentPinchDist = Distance(Input.touches[0].position, Input.touches[1].position) * content.localScale.x;
_currentZoom = (currentPinchDist / _startPinchDist) * _startPinchZoom;
_currentZoom = Mathf.Clamp(_currentZoom, _minZoom, _maxZoom);
}
float Distance(Vector2 pos1, Vector2 pos2)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(content, pos1, null, out pos1);
RectTransformUtility.ScreenPointToLocalPointInRectangle(content, pos2, null, out pos2);
return Vector2.Distance(pos1, pos2);
}
static void SetPivot(RectTransform rectTransform, Vector2 pivot)
{
if (rectTransform == null) return;
Vector2 size = rectTransform.rect.size;
Vector2 deltaPivot = rectTransform.pivot - pivot;
Vector3 deltaPosition = new Vector3(deltaPivot.x * size.x, deltaPivot.y * size.y) * rectTransform.localScale.x;
rectTransform.pivot = pivot;
rectTransform.localPosition -= deltaPosition;
}
}
Thanks, I tried your solution and works like a charm. Just a little caveat: the inside content has not to have a stretch anchor, otherwise things start beco$$anonymous$$g strange. A center anchoring is perfectly ok.
Perfect solution! just use this script in 'Screen Space - Overlay' canvas
Add link to camera to ScreenPointToLocalPointInRectangle
Well, do you know how can we make it with double tap?
This is a great solution and i have rewarded you, but would you $$anonymous$$d explaining what we need to assign to "Viewport" as well as the two scrollbars?
Thank you so much! This really saves me a lot of time.
Answer by SunnyChow · Dec 06, 2016 at 02:29 AM
you could use event trigger to collect all the touches. when there are 2 points, you use their distance this frame and last frame to calculate how much you should scale in this frame
I can zoom in by using touch input but the problem is panning and zoom out.Panning is not smooth enough and while zoo$$anonymous$$g out image snaps. here's my code take a look-:
To smoothen the movement I suggest using interpolation. Have a look at SmoothDamp and Lerp.
Answer by Saad1990 · May 25, 2017 at 02:54 PM
first apply event listener on gameobject like image in this case, then attach c# script with this code
public GameObject image; public static bool flag = false;
public void OnImageTapBeginClick(){
if (flag) {
flag = false;
image.transform.localScale = new Vector2 (1f, 1f);
} else {
flag = true;
image.transform.localScale = new Vector2 (2f,2f);
}
}
Answer by MathiasBlank · Jun 04, 2018 at 08:30 PM
Very useful script NoGame0life :)
If you're using a "clamped movement" your scroll view could goes out of limits when you're zooming out. I think it could be a good idea to add a callback function when the pinching is finished to keeps the scroll view into limits.
Here's a simple way to do this with NoGame0life's script:
private void Update() {
if (Input.touchCount == 2) {
if (!_isPinching) {
_isPinching = true;
OnPinchStart();
}
OnPinch();
} else {
if (_isPinching) {
// - Callback (pinch end)
StartCoroutine(KeepsInLimits());
}
_isPinching = false;
if (Input.touchCount == 0) {
blockPan = false;
}
}/*
IEnumerator KeepsInLimits() {
this.movementType = MovementType.Elastic;
yield return new WaitForSeconds(0.3f); // - for example
this.movementType = MovementType.Clamped;
}
@Herinda can you please add this callback function to the NoGame0life's script, i try to edit the script of nogame0life's script in unity but i have a problem. please can you write the whole script with your edit? thank you
@imad_bzitou Oh yeah! I'm a bite busy this week but I will try to do it friday :).
Hi! Friendly request to share this in this thread :)
Answer by nonameboy · Sep 22, 2019 at 07:02 AM
@imad_bzitou hey imad, it's enough if you set _minZoom to 0.5f in the Awake function. Greets