public static Vector2 GetContentAnchoredPositionBounded(this ScrollRect sr, Vector2 targetAnchoredPos)
    {
        if (sr && sr.content)
        {
            var    contentRtx      = sr.content.transform as RectTransform;
            var    srRtx           = sr.transform as RectTransform;
            Bounds contentBounds   = sr.GetContentBoundsInAnotherSpace((RectTransform)sr.transform);
            Bounds viewBounds      = new Bounds(srRtx.rect.center, srRtx.rect.size);
            bool   allowHorizontal = (sr.movementType == ScrollRect.MovementType.Unrestricted) || contentBounds.size.x > viewBounds.size.x;
            bool   allowVertical   = (sr.movementType == ScrollRect.MovementType.Unrestricted) || contentBounds.size.y > viewBounds.size.y;

            Vector2 offset = sr.CalculateOffsetImpl(targetAnchoredPos - contentRtx.anchoredPosition);
            targetAnchoredPos += offset;

            if (!sr.horizontal || !allowHorizontal)
            {
                targetAnchoredPos.x = contentRtx.anchoredPosition.x;
            }
            if (!sr.vertical || !allowVertical)
            {
                targetAnchoredPos.y = contentRtx.anchoredPosition.y;
            }

            return(targetAnchoredPos);
        }

        return(Vector2.zero);
    }
    static Vector3 CalculateOffsetImpl(this ScrollRect sr, Vector2 delta)
    {
        if (sr)
        {
            Vector3 offset = Vector3.zero;
            if (sr.movementType == ScrollRect.MovementType.Unrestricted)
            {
                return(offset);
            }

            var    srRtx           = sr.transform as RectTransform;
            Bounds srBounds        = new Bounds(srRtx.rect.center, srRtx.rect.size);
            Bounds contentBounds   = sr.GetContentBoundsInAnotherSpace((RectTransform)sr.transform);
            bool   allowHorizontal = (sr.movementType == ScrollRect.MovementType.Unrestricted) || contentBounds.size.x > srBounds.size.x;
            bool   allowVertical   = (sr.movementType == ScrollRect.MovementType.Unrestricted) || contentBounds.size.y > srBounds.size.y;

            Vector2 contentBoundsTranslatedMin = contentBounds.min;
            Vector2 contentBoundsTranslatedMax = contentBounds.max;

            if (sr.horizontal)
            {
                contentBoundsTranslatedMin.x += delta.x;
                contentBoundsTranslatedMax.x += delta.x;
                if (!allowHorizontal || contentBoundsTranslatedMin.x > srBounds.min.x)
                {
                    offset.x = srBounds.min.x - contentBoundsTranslatedMin.x;
                }
                else if (contentBoundsTranslatedMax.x < srBounds.max.x)
                {
                    offset.x = srBounds.max.x - contentBoundsTranslatedMax.x;
                }
            }

            if (sr.vertical)
            {
                contentBoundsTranslatedMin.y += delta.y;
                contentBoundsTranslatedMax.y += delta.y;
                if (!allowVertical || contentBoundsTranslatedMax.y < srBounds.max.y)
                {
                    offset.y = srBounds.max.y - contentBoundsTranslatedMax.y;
                }
                else if (contentBoundsTranslatedMin.y > srBounds.min.y)
                {
                    offset.y = srBounds.min.y - contentBoundsTranslatedMin.y;
                }
            }

            return(offset);
        }

        return(Vector3.zero);
    }
    public static Vector2 GetClosestContentAnchoredPositionBounded(this ScrollRect sr, Vector2 targetAnchoredPosA, Vector2 targetAnchoredPosB)
    {
        if (sr && sr.content)
        {
            var    contentRtx      = sr.content.transform as RectTransform;
            var    srRtx           = sr.transform as RectTransform;
            Bounds contentBounds   = sr.GetContentBoundsInAnotherSpace((RectTransform)sr.transform);
            Bounds viewBounds      = new Bounds(srRtx.rect.center, srRtx.rect.size);
            bool   allowHorizontal = (sr.movementType == ScrollRect.MovementType.Unrestricted) || contentBounds.size.x > viewBounds.size.x;
            bool   allowVertical   = (sr.movementType == ScrollRect.MovementType.Unrestricted) || contentBounds.size.y > viewBounds.size.y;

            Vector2 offsetA = sr.CalculateOffsetImpl(targetAnchoredPosA - contentRtx.anchoredPosition);
            targetAnchoredPosA += offsetA;

            Vector2 offsetB = sr.CalculateOffsetImpl(targetAnchoredPosB - contentRtx.anchoredPosition);
            targetAnchoredPosB += offsetB;


            if (!sr.horizontal || !allowHorizontal)
            {
                targetAnchoredPosA.x = contentRtx.anchoredPosition.x;
                targetAnchoredPosB.x = contentRtx.anchoredPosition.x;
            }
            if (!sr.vertical || !allowVertical)
            {
                targetAnchoredPosA.y = contentRtx.anchoredPosition.y;
                targetAnchoredPosB.y = contentRtx.anchoredPosition.y;
            }

            // we want to get the closest position along A<->B
            float t = 0f;
            if (!Mathf.Approximately(targetAnchoredPosA.x, targetAnchoredPosB.x))
            {
                t = Mathf.InverseLerp(targetAnchoredPosA.x, targetAnchoredPosB.x, contentRtx.anchoredPosition.x);
            }
            else
            {
                t = Mathf.InverseLerp(targetAnchoredPosA.y, targetAnchoredPosB.y, contentRtx.anchoredPosition.y);
            }

            return(Vector2.Lerp(targetAnchoredPosA, targetAnchoredPosB, t));
        }

        return(Vector2.zero);
    }