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 );
}
}
@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

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