diff --git a/core/src/components/segment/segment.tsx b/core/src/components/segment/segment.tsx index c96425c8026..c015d7e617b 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,30 +311,52 @@ 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', + el.scrollBy({ + top: 0, + left: centeredX, + behavior: smoothScroll ? 'smooth' : 'instant', }); } }