Skip to content

Instantly share code, notes, and snippets.

@yasirkula
Created October 23, 2021 10:09
Show Gist options
  • Save yasirkula/75ca350fb83ddcc1558d33a8ecf1483f to your computer and use it in GitHub Desktop.
Save yasirkula/75ca350fb83ddcc1558d33a8ecf1483f to your computer and use it in GitHub Desktop.
Focus/center Scroll View to the specified point/item in Unity
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public static class ScrollViewFocusFunctions
{
public static Vector2 CalculateFocusedScrollPosition( this ScrollRect scrollView, Vector2 focusPoint )
{
Vector2 contentSize = scrollView.content.rect.size;
Vector2 viewportSize = ( (RectTransform) scrollView.content.parent ).rect.size;
Vector2 contentScale = scrollView.content.localScale;
contentSize.Scale( contentScale );
focusPoint.Scale( contentScale );
Vector2 scrollPosition = scrollView.normalizedPosition;
if( scrollView.horizontal && contentSize.x > viewportSize.x )
scrollPosition.x = Mathf.Clamp01( ( focusPoint.x - viewportSize.x * 0.5f ) / ( contentSize.x - viewportSize.x ) );
if( scrollView.vertical && contentSize.y > viewportSize.y )
scrollPosition.y = Mathf.Clamp01( ( focusPoint.y - viewportSize.y * 0.5f ) / ( contentSize.y - viewportSize.y ) );
return scrollPosition;
}
public static Vector2 CalculateFocusedScrollPosition( this ScrollRect scrollView, RectTransform item )
{
Vector2 itemCenterPoint = scrollView.content.InverseTransformPoint( item.transform.TransformPoint( item.rect.center ) );
Vector2 contentSizeOffset = scrollView.content.rect.size;
contentSizeOffset.Scale( scrollView.content.pivot );
return scrollView.CalculateFocusedScrollPosition( itemCenterPoint + contentSizeOffset );
}
public static void FocusAtPoint( this ScrollRect scrollView, Vector2 focusPoint )
{
scrollView.normalizedPosition = scrollView.CalculateFocusedScrollPosition( focusPoint );
}
public static void FocusOnItem( this ScrollRect scrollView, RectTransform item )
{
scrollView.normalizedPosition = scrollView.CalculateFocusedScrollPosition( item );
}
private static IEnumerator LerpToScrollPositionCoroutine( this ScrollRect scrollView, Vector2 targetNormalizedPos, float speed )
{
Vector2 initialNormalizedPos = scrollView.normalizedPosition;
float t = 0f;
while( t < 1f )
{
scrollView.normalizedPosition = Vector2.LerpUnclamped( initialNormalizedPos, targetNormalizedPos, 1f - ( 1f - t ) * ( 1f - t ) );
yield return null;
t += speed * Time.unscaledDeltaTime;
}
scrollView.normalizedPosition = targetNormalizedPos;
}
public static IEnumerator FocusAtPointCoroutine( this ScrollRect scrollView, Vector2 focusPoint, float speed )
{
yield return scrollView.LerpToScrollPositionCoroutine( scrollView.CalculateFocusedScrollPosition( focusPoint ), speed );
}
public static IEnumerator FocusOnItemCoroutine( this ScrollRect scrollView, RectTransform item, float speed )
{
yield return scrollView.LerpToScrollPositionCoroutine( scrollView.CalculateFocusedScrollPosition( item ), speed );
}
}
@yasirkula
Copy link
Author

How To

After adding the above C# script to your project, simply call scrollView.FocusOnItem( targetItem ). To focus on that item smoothly, call StartCoroutine( scrollView.FocusOnItemCoroutine( targetItem, focusSpeed ) ). Don't call FocusOnItemCoroutine every frame, wait for it to finish first.

@burst2flame
Copy link

Huge help. Thanks!

@GuyGinat
Copy link

Wow

@mh-studios
Copy link

Thanks a lot for sharing this. Great!!!

@DeniMN
Copy link

DeniMN commented Apr 21, 2023

Thank you! I had my own system, but yours is more accurate

@MohamadJavadGholizade
Copy link

Thank you, You are awesome man

@Enlumis
Copy link

Enlumis commented Jun 28, 2023

Really nice thank you !
for those who want to chain up scroll coroutine and cancel the previous one to avoid glitches.
here are some new coroutine functions

	public static bool cancelOutPreviousCoroutine = false;
	public static int runningLerpCoroutine = 0;
	private static IEnumerator LerpToScrollPositionCoroutine( this ScrollRect scrollView, Vector2 targetNormalizedPos, float speed )
	{
		Vector2 initialNormalizedPos = scrollView.normalizedPosition;

		float t = 0f;
		while( t < 1f )
		{
			
			scrollView.normalizedPosition = Vector2.LerpUnclamped( initialNormalizedPos, targetNormalizedPos, 1f - ( 1f - t ) * ( 1f - t ) );

			yield return null;
			t += speed * Time.unscaledDeltaTime;
			
			if (cancelOutPreviousCoroutine) {
				t = 1;
				cancelOutPreviousCoroutine = false;
			}
		
			if (t < 1f) {
				scrollView.normalizedPosition = targetNormalizedPos;
			}
		}
		
		runningLerpCoroutine--;
	}

	public static IEnumerator FocusAtPointCoroutine( this ScrollRect scrollView, Vector2 focusPoint, float speed )
	{
		if (runningLerpCoroutine > 0) cancelOutPreviousCoroutine = true;
		runningLerpCoroutine++;
		yield return scrollView.LerpToScrollPositionCoroutine( scrollView.CalculateFocusedScrollPosition( focusPoint ), speed );
	}

	public static IEnumerator FocusOnItemCoroutine( this ScrollRect scrollView, RectTransform item, float speed )
	{
		if (runningLerpCoroutine > 0) cancelOutPreviousCoroutine = true;
		runningLerpCoroutine++;
		yield return scrollView.LerpToScrollPositionCoroutine( scrollView.CalculateFocusedScrollPosition( item ), speed );
	}

@ViacheslavWeaver
Copy link

I can't express how I thank you. This works just perfect! Thank you very much.

@alamin-khalid
Copy link

Thanks

@hadisajjadi
Copy link

Great. thank you

@gimpycpu
Copy link

gimpycpu commented Feb 2, 2024

Hello sir, I'd like to use your code but there is no license could you please add one when you have time :)

Thank you

@yasirkula
Copy link
Author

@gimpycpu Hi! Feel free to use it under MIT License.

@jcosmick
Copy link

thanks man, your are a saviour for my stupid brain

@TrungNVX
Copy link

public static bool cancelOutPreviousTask = false;
public static int runningLerpTask = 0;

private static async UniTask LerpToScrollPositionAsync(this ScrollRect scrollView, Vector2 targetNormalizedPos, float speed)
{
    Vector2 initialNormalizedPos = scrollView.normalizedPosition;
    float t = 0f;

    while (t < 1f)
    {
        scrollView.normalizedPosition = Vector2.LerpUnclamped(initialNormalizedPos, targetNormalizedPos, 1f - (1f - t) * (1f - t));
        await UniTask.Yield(PlayerLoopTiming.Update);
        t += speed * Time.unscaledDeltaTime;

        if (cancelOutPreviousTask)
        {
            t = 1;
            cancelOutPreviousTask = false;
        }
    }

    scrollView.normalizedPosition = targetNormalizedPos;
    runningLerpTask--;
}

public static async UniTask FocusAtPointAsync(this ScrollRect scrollView, Vector2 focusPoint, float speed)
{
    if (runningLerpTask > 0) cancelOutPreviousTask = true;
    runningLerpTask++;
    await scrollView.LerpToScrollPositionAsync(scrollView.CalculateFocusedScrollPosition(focusPoint), speed);
}

public static async UniTask FocusOnItemAsync(this ScrollRect scrollView, RectTransform item, float speed)
{
    if (runningLerpTask > 0) cancelOutPreviousTask = true;
    runningLerpTask++;
    await scrollView.LerpToScrollPositionAsync(scrollView.CalculateFocusedScrollPosition(item), speed);
}

I prefer Unitask rather than old coroutine, here are some refactor codes

@Hexusz
Copy link

Hexusz commented Jul 5, 2024

If you are using a content size filter and you have the position set incorrectly, use Canvas.ForceUpdateCanvases() before calling it

@ryushinnn
Copy link

thank you so much, it works perfectly 💯

@kurukahve0
Copy link

Thank you :)

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