From f04980adc86f916b5bc25fd1d9aec95b52bf4708 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Wed, 18 Oct 2023 10:21:12 -0400 Subject: [PATCH 1/3] fix(segment): avoid scrolling webkit bug --- core/src/components/segment/segment.tsx | 66 ++++++++++++++++++------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/core/src/components/segment/segment.tsx b/core/src/components/segment/segment.tsx index c96425c8026..db152c8a0a8 100644 --- a/core/src/components/segment/segment.tsx +++ b/core/src/components/segment/segment.tsx @@ -145,7 +145,13 @@ export class Segment implements ComponentInterface { * before we can scroll. */ raf(() => { - this.scrollActiveButtonIntoView(); + /** + * When the segment loads for the first + * time we just want to snap the active button into + * place instead of scroll. Smooth scrolling should only + * happen when the user interacts with the segment. + */ + this.scrollActiveButtonIntoView(false); }); this.gesture = (await import('../../utils/gesture')).createGesture({ @@ -305,31 +311,53 @@ export class Segment implements ComponentInterface { } } - private scrollActiveButtonIntoView() { - const { scrollable, value } = this; + private scrollActiveButtonIntoView(smoothScroll = true) { + const { scrollable, value, el } = this; if (scrollable) { const buttons = this.getButtons(); const activeButton = buttons.find((button) => button.value === value); if (activeButton !== undefined) { + const scrollContainerBox = el.getBoundingClientRect(); + const activeButtonBox = activeButton.getBoundingClientRect(); + + /** + * Subtract the active button x position from the scroll + * container x position. This will give us the x position + * of the active button within the scroll container. + */ + const activeButtonLeft = activeButtonBox.x - scrollContainerBox.x; + + /** + * If we just used activeButtonLeft, then the active button + * would be aligned with the left edge of the scroll container. + * Instead, we want the segment button to be centered. As a result, + * we subtract half of the scroll container width. This will position + * the left edge of the active button at the midpoint of the scroll container. + * We then add half of the active button width. This will position the active + * button such that the midpoint of the active button is at the midpoint of the + * scroll container. + */ + const centeredX = activeButtonLeft - (scrollContainerBox.width / 2) + (activeButtonBox.width / 2); + /** - * Scrollable segment buttons should be - * centered within the view including - * buttons that are partially offscreen. + * We intentionally use scrollBy here instead of scrollIntoView + * to avoid a WebKit bug where accelerated animations break + * when using scrollIntoView. Using scrollIntoView will cause the + * segment container to jump during the transition and then snap into place. + * This is because scrollIntoView can potentially cause parent element + * containers to also scroll. scrollBy does not have this same behavior, so + * we use this API instead. + * + * Note that if there is not enough scrolling space to center the element + * within the scroll container, the browser will attempt + * to center by as much as it can. */ - activeButton.scrollIntoView({ - behavior: 'smooth', - inline: 'center', - - /** - * Segment should scroll on the - * horizontal axis. `block: 'nearest'` - * ensures that the vertical axis - * does not scroll if the segment - * as a whole is already in view. - */ - block: 'nearest', - }); + this.el.scrollBy({ + top: 0, + left: centeredX, + behavior: smoothScroll ? 'smooth' : 'instant' + }) } } } From 854f3e2c19e7eeca783b3aee49a524d52d5ad214 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Wed, 18 Oct 2023 10:22:25 -0400 Subject: [PATCH 2/3] clean up --- core/src/components/segment/segment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/components/segment/segment.tsx b/core/src/components/segment/segment.tsx index db152c8a0a8..08e107a26fc 100644 --- a/core/src/components/segment/segment.tsx +++ b/core/src/components/segment/segment.tsx @@ -353,7 +353,7 @@ export class Segment implements ComponentInterface { * within the scroll container, the browser will attempt * to center by as much as it can. */ - this.el.scrollBy({ + el.scrollBy({ top: 0, left: centeredX, behavior: smoothScroll ? 'smooth' : 'instant' From d6a69f652d02c23469193608e08a4a2cd332fa3d Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Wed, 18 Oct 2023 10:35:29 -0400 Subject: [PATCH 3/3] lint --- core/src/components/segment/segment.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/components/segment/segment.tsx b/core/src/components/segment/segment.tsx index 08e107a26fc..c015d7e617b 100644 --- a/core/src/components/segment/segment.tsx +++ b/core/src/components/segment/segment.tsx @@ -145,12 +145,12 @@ export class Segment implements ComponentInterface { * before we can scroll. */ raf(() => { - /** - * When the segment loads for the first - * time we just want to snap the active button into - * place instead of scroll. Smooth scrolling should only - * happen when the user interacts with the segment. - */ + /** + * When the segment loads for the first + * time we just want to snap the active button into + * place instead of scroll. Smooth scrolling should only + * happen when the user interacts with the segment. + */ this.scrollActiveButtonIntoView(false); }); @@ -338,7 +338,7 @@ export class Segment implements ComponentInterface { * button such that the midpoint of the active button is at the midpoint of the * scroll container. */ - const centeredX = activeButtonLeft - (scrollContainerBox.width / 2) + (activeButtonBox.width / 2); + const centeredX = activeButtonLeft - scrollContainerBox.width / 2 + activeButtonBox.width / 2; /** * We intentionally use scrollBy here instead of scrollIntoView @@ -356,8 +356,8 @@ export class Segment implements ComponentInterface { el.scrollBy({ top: 0, left: centeredX, - behavior: smoothScroll ? 'smooth' : 'instant' - }) + behavior: smoothScroll ? 'smooth' : 'instant', + }); } } }