Open Source Scroll Snap Project - Unity UI Scroll Snaps

Unity UI Scroll Snaps

Unity UI Scroll Snaps is a new open source project focused on creating robust components for all of your scrolling and snapping needs.

This was originally inspired by an idea I had for a menu system where you could scroll and select item options, but even though I saw some amazing extensions of the Unity Scroll Rect I couldn't find any solutions that had adaptable spacing for different sized items, so I decided to create one myself! After much tinkering and many additions I decided the script I'd written was ready to take it's first steps into the broader world, and maybe even meet some other friendly Scroll Snap scripts out there!

New Version!

This project now has a new Version 1, completely rewritten from scratch! You can go here to check it out, and learn why this change has happened. Version 0 is still (for now) the official version because it is the most fully featured, but in the future cool stuff will be happening over there .

Creative Tenets

  • Components should be as self-contained as possible, ease of installment for the user takes priority over minimizing duplicate code.
  • Components should not rely on vanilla Unity Components like Scroll Rects, they should handle whatever they need to do themselves, to make it easier for the user.
  • What you see is what you get, when the user is setting up the component in the editor it should look the same as how it will look when they run it.

All of these things help ensure a good experience for the user. No more making sure you have the 5 scripts this component needs to function, no more redundant components cluttering up your gameobjects, and especially no more uncertainty about how something will look after you hit play.

Contributing

This project is specifically for Scroll Snaps, things that scroll and then snap to items. If you have a lovely UI component that you're just dying to put somewhere, but it doesn't have to do with scroll snaps, consider contributing it to the Unity UI Extensions project.

If you do indeed have a Scroll Snap component that you want to show the world we would love to take it! You can find out more about contributing here and here. Just be prepared PRs will probably get commented on :p

Downloading

Download instructions can be found here, or if you just want the latest asset package right now here it is for your enjoyment.

List of All the Things

Examples of "all the things" in action are part of the asset package or can be downloaded individually here. You can also check out the List of All the Things for Version 1 here .

Components [spoiler][/spoiler]Components

[spoiler]

Directional Scroll Snap

The Directional Scroll Snap is based on the idea of the scrollable item menu. Items are "snapped to" (aligned with the center of the Directional Scroll Snap) along one axis so that it either scrolls vertically or horizontally. The Directional Scroll Snap also snaps to items pivots not their centers (although you can set it to their center) to give you more options in how you animate & organize your items.

Options Include:

  • Moving Horizontally or Vertically
  • Locking or Unlocking the non-moving direction. (so it can be loosey goosey if you want)
  • Looping functionality
  • Use velocity when snapping, or don't.
  • Different snap types: snap to nearest, snap to last passed, and snap to next.
  • Different Interpolators for scrolling animations, it can overshoot, anticipate, do a little dance, ect.
  • The ability to override Interpolators (among other things) for different input types.
  • Button control for going back (up/left) and forward (down/right)
  • Scrollbar control (which can use velocity or not, whatever suites your fancy)
  • Scroll wheel & touch pad control (ditto above)
  • Mouse pointer & touch control (you get the drill)
  • Filtering of items, you can tell the Scroll Snap what it is and is not allowed to snap to, and what it is and is not allowed to calculate its size with. So you can have unsnap-to-able descriptive text & decorative edging that stretches to the size of the content. So pretty!
  • Supports placing items "by hand" (whatever that means) as well as Horizontal Layout Group, Vertical Layout Group, and Grid Layout Group.
  • Supports populating and editting the Scroll Snap at runtime through functions like: InsertChild, RemoveChild, SetChildSnappability and more.
  • Plus lots of Unity events to hook into including, but not limited to: Start Movement, Snapped to Item, and Closest Item Changed.

Omni Directional Scroll Snap

The Omni Directional Scroll Snap is based on the idea of the item/skill tree. The intention being that you could move the Scroll Snap vertically, horizontally, diagonally... or even a bit of each, and when you stopped moving it it would snap to the nearest item. When the Omni Directional Scroll Snap snaps to an item it aligns the item both horizontally and vertically, so the pivot of the item is at the dead center of the Scroll Snap.

Options Include:

  • Using velocity when snapping, or not.
  • Different Interpolators for scrolling animations, including crowd favorites like: Viscous Fluid, Overshoot, Accelerate Decelerate, and many more.
  • The ability to override interpolators (among other things) for different input types.
  • Any control method you can think of! We've got scrollbar control, scroll wheel control, touch pads, mouse drags, and touch control.
  • Filtering of Items, letting you tell the Scroll Snap what it is allowed to snap to, and what it is allowed to calculate its size with. So you can add some some lovely unsnap-to-able lines between the items in your skill tree, and decorative edging that stretches to the size of the content. All the options you could only dream of!
  • Supports placing items with individual love and care, or throwing them in a Layout Group (Horizontal Layout Group, Vertical Layout Group, or Grid Layout Group) and letting the computer do the work.
  • Supports populating and editting the Scroll Snap at runtime through functions like: InsertChild, RemoveChild, SetChildSnappability and more.

  • Plus 5 different Unity Events you can hook into including (but not limited to) your old friend On Value Changed, your new friend Start Movement Event, and your new new friend On Closest Item Changed.

[/spoiler]

Helper Classes

[spoiler]

Scroller

The Scroller is based on the Android Open Source Project's Scroller. It makes it easy to move something from point A to point B in way that's cleaner and more robust than simple lerping.

Options Include:

  • Interpolators for scrolling animations. It comes with a smattering of built in interpolators that allow you to change up your scrolling animations, such as Viscous Fluid, Overshoot, and Anticipate, or you can create your own interpolator for it to use.
  • Animations based on velocity (Flings) and animations based on duration (Scrolls).
  • Functions for calculating where a Fling will land before it has been flung.

[/spoiler]

2 Likes

Updates to Directional Scroll Snap:

  • Added support for Grid Layout Groups.
  • Fixed Horizontal & Vertical Layout group support in the Directional Scroll Snap.
  • Now the Directional Scroll Snap never changes position of items when it starts driving their RectTransforms.
  • Fixed some other minor bugs in the Directional Scroll Snap.

good job!

1 Like

Added New Component Omni Directional Scroll Snap:

Description

[spoiler]

The Omni Directional Scroll Snap is based on the idea of the item/skill tree. The intention being that you could move the Scroll Snap vertically, horizontally, diagonally... or even a bit of each, and when you stopped moving it it would snap to the nearest item. When the Omni Directional Scroll Snap snaps to an item it aligns the item both horizontally and vertically, so the pivot of the item is at the dead center of the Scroll Snap.

Options Include:

  • Using velocity when snapping, or not.
  • Different Interpolators for scrolling animations, including crowd favorites like: Viscous Fluid, Overshoot, Accelerate Decelerate, and many more.
  • Any control method you can think of! We've got scrollbar control, scroll wheel control, touch pads, mouse drags, and touch control.
  • Filtering of Items, letting you tell the Scroll Snap what it is allowed to snap to, and what it is allowed to calculate its size with. So you can add some some lovely unsnap-to-able lines between the items in your skill tree, and decorative edging that stretches to the size of the content. All the options you could only dream of!
  • Supports placing items with individual love and care, or throwing them in a Layout Group (Horizontal Layout Group, Vertical Layout Group, or Grid Layout Group) and letting the computer do the work.
  • Plus 5 different Unity Events you can hook into including (but not limited to) your old friend On Value Changed, your new friend Start Movement Event, and your new new friend On Closest Item Changed.

[/spoiler]

The repository and download links all include examples of this new Component so you can get started playing with it right away. If you have any questions about it or questions about anything else feel free to ask!

Updates to Directional Scroll Snap:

  • Added a new Snap Type: Snap To Next. The Directional Scroll Snap previously only had the options: Snap to Nearest and Snap To Last Passed.
  • Removed code for keeping item position consistent because it was causing problems.
  • Moved the Directional Scroll Snap Editor into the UnityEngine.UI.ScrollSnaps namespace.
  • Fixed a couple other little bugs and typos.

wow!

Have you plans to move on to create a infinite scroll and reusign cells to do this?

[quote=“sp-sergio-gil”, post:6, topic: 693194]
Have you plans to move on to create a infinite scroll and reusign cells to do this?
[/quote]

An infinite scrolling/circular scrolling component is next on my list! Right now I’m working on deciding if it would work better as an addition to the Directional Scroll Snap, or as a separate component with it’s own capabilities. If you have any opinions feel free to leave a comment or shoot me a message.

If I can I will check your code to be sure how is splitted, but perhaps the good point could be having a component that enables having a reusable or a normal scroll with the infinite scroll option and then have another component that would be the snap + pagination behaviour?

Aside that, it could be usefull to have an option to define a desired starting cell to snap at the beginning?

[quote=“sp-sergio-gil”, post:8, topic: 693194]
… a component that enables having a reusable or a normal scroll with the infinite scroll option and then have another component that would be the snap + pagination behavior?
[/quote]

If I’m reading this right it looks like you’re looking for a Component that just does the infinite/circular scrolling without any snapping behavior?

While I definitively want to do some stuff with infinite/circular scrolling I also want all of the Components to have some sort of snapping and some sort of scrolling mechanic - hence the name Unity UI Scroll Snaps hehe. I know that makes the project pretty limited in its scope but I think that helps maintain the focus on making the Components it does contain the best they can be.

If you are indeed looking for a simple infinite/circular scrolling solution I believe the Unity UI Extensions project has one here.

Otherwise I’m sorry I read your comment wrong and I do still want to add infinite/circular capabilities! Just along with snapping.

[quote=“sp-sergio-gil”, post:8, topic: 693194]
Aside that, it could be useful to have an option to define a desired starting cell to snap at the beginning?
[/quote]

Good idea! There probably won’t be scrolling to the start cell, because if I remember correctly from past work starting up a scrolling animation on frame 1 gives a pretty terrible choppy effect, but I can definitely make it so that when you start it up it’s already snapped to a cell. I’ll add it to my list!

Hey! I like the idea of having a component that does all the things (infinite, reusable, snap,...) the think is that I trying only to give my opinion on how I would like to proceed... like having all in the same component, having different components related,...

As you said, I was trying to help and giving my opinion

Hey this is cool!!!
Any idea how I could magnify the selected one as theyre moving, like a smooth magnification of the centred item?
Thanks heaps for this project.

1 Like

Very useful for us when create scroll snap view, thank you very much! :smile:

1 Like

[quote]
Any idea how I could magnify the selected one as theyre moving, like a smooth magnification of the centred item?
[/quote]

Ok I have two pretty simple solutions for you! I based all of this on the Directional Scroll Snap, but the idea behind both solutions should work for either the Direction or OmniDirectional components, you may just have to switch around a few lines of code.

And just for reference my Directional Scroll Snap's snap mode is set to SnapToNext.

Solution 1

[spoiler]

This creates a sort of "gradient" of scale based on an Animation Curve. The items closest to the center of the scroll snap are larger than the items further away, and whenever the scroll snap moves it updates.

You're going to need access to the Calculate Children of the scroll snap (unless you only want to scale the Snap Children, in that case grab those) so you're going to need to add two simple lines of code to the Directional Scroll Snap (this will be added in an up-comming update [EDIT: Added]).

That done you're going to need to create a class like this:

using System.Collections;
using UnityEngine;
using UnityEngine.UI.ScrollSnaps;

public class ScaleBasedOnDistance : MonoBehaviour {

    public DirectionalScrollSnap scrollSnap;
    public RectTransform scrollSnapRectTransform;
    public AnimationCurve scaleCurve;

    public void Start()
    {
        scrollSnap.UpdateLayout();
        UpdateScale();
    }

    public void OnValueChanged(Vector2 normalizedPos)
    {
        UpdateScale();
    }

    private void UpdateScale()
    {
        foreach (RectTransform child in scrollSnap.calculateChildren)
        {
            float scale = scaleCurve.Evaluate(Vector3.Distance(scrollSnapRectTransform.position, child.position));
            child.localScale = new Vector3(scale, scale, scale);
        }
    }
}

Attach that to a GameObject in your Scene (I attached mine to my Directional Scroll Snap), fill in the variables, and add the OnValueChanged function to the Direction Scroll Snap's OnValueChanged event.

The Animation Curve really determines the look and feel of this solution. In this first example I set it up so that anything further away than 500 had a scale of 1, and because the farthest away an item can be is 500 it creates a scale "gradient" across all of the items.

Curve 1

[spoiler]

[/spoiler]

Video Link

In the second example I set the "max distance" to be 200, this makes it so the closest 1 or 2 items are the only ones affected.

Curve 2

[spoiler]

[/spoiler]

Video Link

The solution you use for your specific senario may be different (e.g. you may want to manipulate the item's size delta's directly, or change their color instead of their scale) but hopefully this gives you one idea of how you could acheive this!

[/spoiler]

Solution 2

[spoiler]

This is a solution for only manipulating the target item and the previous target item, so it doesn't create the "gradient" of scale that was in the previous solution. Everything here is based on animation times.

First you're going to need to fix a bug I discovered while creating this (I'll put up a fix for this soon [EDIT: Fixed]) all you need to do is change the > sign in this function to a >= sign. (The picture has it fixed)

And then you're going to need to create a class like this:

using System.Collections;
using UnityEngine;
using UnityEngine.UI.ScrollSnaps;

public class ScaleBasedTarget : MonoBehaviour {

    public DirectionalScrollSnap scrollSnap;
    public RectTransform scrollSnapRectTransform;
    public AnimationCurve scaleCurve;

    private RectTransform targetItem;
    private RectTransform previousTargetItem;

    public void Start()
    {
        scrollSnap.UpdateLayout();

        scrollSnap.GetChildAtIndex(scrollSnap.closestSnapPositionIndex, out selectedItem);
        selectedItem.localScale = new Vector3(1.5f, 1.5f, 1.5f);
    }

    public void TargetItemSelected(int snapIndex)
    {
        previousTargetItem = targetItem;
        scrollSnap.GetChildAtIndex(snapIndex, out targetItem);
        StartCoroutine(ScaleSelectedItems());
    }

    private IEnumerator ScaleSelectedItems()
    {
        while (scrollSnap.scroller.timePassedSinceStartOfAnimation < scrollSnap.scroller.durationOfLatestAnimation)
        {
            if (previousTargetItem != null)
            {
                float scale = Mathf.Lerp(1.5f, 1f, scrollSnap.scroller.timePassedSinceStartOfAnimation / (float)scrollSnap.scroller.durationOfLatestAnimation);
                previousTargetItem.localScale = new Vector3(scale, scale, scale);
            }

            if (targetItem != null)
            {
                float scale = Mathf.Lerp(1f, 1.5f, scrollSnap.scroller.timePassedSinceStartOfAnimation / (float)scrollSnap.scroller.durationOfLatestAnimation);
                targetItem.localScale = new Vector3(scale, scale, scale);
            }

            yield return null;
        }
    }
}

Attach that to a GameObject in your Scene (I attached mine to my Directional Scroll Snap), fill in the variables, and add the TargetItemSelected function to the Direction Scroll Snap's TargetItemChanged event.

Video Link

This achieves an effect that only manipulates objects once the TargetItem has been selected. It is not a robust solution (in this form at least) because it doesn't handle things like people moving the Scroll Snap in the middle of an animation, but hopefully it gives you an idea of a different way you could handle the same problem!

[/spoiler]

Sorry it took me so long to get back to you! And sorry that I wrote such a wall of text for your 1 line question hehe. (I may have gotten a bit carried away.) Tell me if you have any feedback about the examples above or about the Scroll Snap project in general!

Update: More Accessors and some Bug Fixes
[Edit: Update 0.5.0]

Directional Scroll Snap:

  • Added calculateChildren property and snapChildren property, which gives you a list of the children used for calculating and snapping (respectively) - from Start to End.
  • Added a closestItem property so you don't have to go through the bother of scrollSnap.GetChildAtIndex(scrollSnap.closestSnapPositionIndex, out child);
  • Fixed bug with all of the GetAtIndex functions, so now they will let you get things at index 0
  • Added #if statements around all of the UnityEditor pieces, so it should work with builds better now (yay!)
  • And fixed some other minor bugs.

OmniDirectional Scroll Snap:

  • Added calculateChildrenTopToBottom, calculateChildrenLeftToRight, snapChildrenTopToBottom, and snapChildrenLeftToRight properties, so now you have access to the OmniDirectional Scroll Snap's children in whatever order you so choose.
  • Added #if statements around all of the UnityEditor pieces.
  • And fixed some other minor bugs.

Scroller:

  • Added public properties so you can read the Scroller's friction, minDuration, and maxDuration after it has been created.

This is amazing, thank you! I really like option one, curve two. I've got it setup and all working perfectly :smile:

1 Like

Hi ,i am using DirectionalScrollSnap . i have noticed that it only starts from far left side of scroll . what if i want to start from right and move towards left ? is it possible ?

[quote]
i have noticed that it only starts from far left side of scroll .
[/quote]

Currently the Directional Scroll Snap starts wherever it is in edit mode. So if you have the Content positioned so that it's towards the left it will start on the left, and if you have the Content positioned on the right it will start on the right.

Try moving your Content so that whatever item you would like the Scroll Snap to start on is centered.

Image

[spoiler]

[/spoiler]

Video Link

In the future I'm hoping to add a public variable so you can specify a starting item (as sp-sergio-gil suggested), but alas this is not available yet.

Tell me if this helps with your problem or if you were talking about something different! Ty for the feedback!

Hi, Actually i was adding data on runtime not in editor mode . So what i did it i scalled content with Vector(-1,1,1) and all the childs under content with (-1,1,1) also . now it is moving from right towards left

there is another issue . sometimes it doesn't snap to next child and give this error "
Child: AVPro Video(Clone) is outside the valid bounds of the content. If this was unintentional move it inside region indicated by the green arrow gizmos. If you see no green arrows turn on Draw Gizmos.
UnityEngine.Canvas:SendWillRenderCanvases()
"

i can confirm it does not happen because of my (-1,1,1) scalling .

That sounds like a very interesting solution, but sadly that's not how the Directional Scroll Snap is setup to work. Let me see if some pictures can help.

So I've turned on the Draw Gizmos bool to hopefully show what I'm talking about better. This Scroll Snap's content's scale is set to 1,1,1 and as you can see three of the items have cyan lines going through their pivots (meaning they're snappable), while the one to the left is outlined in red. This is because it is outside the bounds of the placeable region, meaning if the Directional Scroll Snap tried to calculate its content's size right now it would throw the warning you're describing.

Image

[spoiler]

[/spoiler]

All of the child items, that you want used in size calculation and snapping, must be inside the region indicated by the green arrows.

Now if we look at the Scroll Snap where the content has a scale of -1,1,1 you can see that three of the items are outlined in red, while the one to the right has a cyan line through it (meaning it's snappable). This is because the scale flip is confusing the Directional Scroll Snap.

Image

[spoiler]

[/spoiler]

All of this is to say that you generally want to keep the content's scale set to 1,1,1 and when populating your scroll snap keep the top left corner in mind.

Here is a solution for populating your scroll snap programmatically (either from start -> end or from end -> start) while keeping the scale at 1,1,1. This also aligns the content on the right after it finishes (or at the bottom in the case of vertical scroll snaps) but this can be changed as well.

Just be sure to fill in all public variables through the inspector, and change line 27 to your prefered fill method before testing it out.

using UnityEngine;
using UnityEngine.UI.ScrollSnaps;

public class PopulateScrollSnap : MonoBehaviour {


    public DirectionalScrollSnap scrollSnap;

    //if you're moving horizontally this will be how far below the top
    //of the content you want your items to be. (-100, -200, ect)
    //if you're moving vertically this is how far right of the left
    //of the content you want your items to be. (100, 200, ect)
    public float nonMovementAxisPos;

    //this is the distance between the pivots of the items (for simplicity)
    //if you want to set the distances between the edges of the items
    //(because, for example, you have different sized items) you can do that
    //it just takes some fancier coding.
    public float distance;

    public GameObject prefab;

    public int amount;

    public void Start()
    {
        //Either of the Fill Functions here

        scrollSnap.UpdateLayout();
        scrollSnap.horizontalNormalizedPosition = 1;
        //or scrollSnap.verticalNormalizedPosition = 1; if you're moving vertically
    }

    public void FillFromStartToEnd()
    {
        for (int i = 0; i < amount; i++)
        {
            GameObject GO = Instantiate(prefab, scrollSnap.content);
            //do any other setup on the GO around here

            RectTransform goRect = GO.GetComponent<RectTransform>();
            goRect.anchorMin = new Vector2(0, 1);
            goRect.anchorMax = new Vector2(0, 1);

            //if we're moving horizontally we want to modify the X axis (0) otherwise
            //we want to modify the Y axis (1)
            int movementAxis = (scrollSnap.movementDirection == DirectionalScrollSnap.MovementDirection.Horizontal) ? 0 : 1;
            int nonMovementAxis = 1 - movementAxis;

            //if we're moving horizontally the end is towards the right (positive)
            //but if we're moving vertically the end is towards the bottom (negative)
            //hence the multiplier
            int directionMultiplier = (scrollSnap.movementDirection == DirectionalScrollSnap.MovementDirection.Horizontal) ? 1 : -1;

            Vector2 newGOAnchorPos = Vector2.zero;
            newGOAnchorPos[movementAxis] = distance * i * directionMultiplier;
            newGOAnchorPos[nonMovementAxis] = nonMovementAxisPos;

            goRect.anchoredPosition = newGOAnchorPos;
        }
    }

    public void FillFromEndToStart()
    {
        for (int i = 0; i < amount; i++)
        {
            GameObject GO = Instantiate(prefab, scrollSnap.content);
            //do any other setup on the GO around here

            RectTransform goRect = GO.GetComponent<RectTransform>();
            goRect.anchorMin = new Vector2(0, 1);
            goRect.anchorMax = new Vector2(0, 1);

            //if we're moving horizontally we want to modify the X axis (0) otherwise
            //we want to modify the Y axis (1)
            int movementAxis = (scrollSnap.movementDirection == DirectionalScrollSnap.MovementDirection.Horizontal) ? 0 : 1;
            int nonMovementAxis = 1 - movementAxis;

            //if we're moving horizontally the end is towards the right (positive)
            //but if we're moving vertically the end is towards the bottom (negative)
            //hence the multiplier
            int directionMultiplier = (scrollSnap.movementDirection == DirectionalScrollSnap.MovementDirection.Horizontal) ? 1 : -1;

            Vector2 newGOAnchorPos = Vector2.zero;
            newGOAnchorPos[movementAxis] = ((distance * amount) - (distance * i)) * directionMultiplier;
            newGOAnchorPos[nonMovementAxis] = nonMovementAxisPos;

            goRect.anchoredPosition = newGOAnchorPos;
        }
    }
}

Now I know that class probably won't solve all of your problems (and I mean really what would be the fun in it if it did), but hopefully it gives you a starting place to work from!


PS: In the next update public functions for adding items programmatically will be added*, so you don't have to go through this hassle yourself, along with looping functionally.*

*This will only be added to the Directional Scroll Snap for now though.