Fixed layout thrashing when `Stream Fade-In` and `Auto-scroll Chat` are enabled. (#4791) * Scheduling scrollChatToBottom after the animation frame prevents layout thrashing. https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value https://gist.github.com/paulirish/5d52fb081b3570c81e3a#file-what-forces-layout-md * Make frame request optional --------- Co-authored-by: user <user@exmaple.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -1451,7 +1451,7 @@ function scrollOnMediaLoad() { | |||
| 1451 | } | 1451 | } |
| 1452 | mediaLoaded++; | 1452 | mediaLoaded++; |
| 1453 | if (mediaLoaded === media.length) { | 1453 | if (mediaLoaded === media.length) { |
| 1454 | scrollChatToBottom(); | 1454 | scrollChatToBottom({ waitForFrame: true }); |
| 1455 | } | 1455 | } |
| 1456 | } | 1456 | } |
| 1457 | } | 1457 | } |
| @@ -2598,8 +2598,19 @@ function formatGenerationTimer(gen_started, gen_finished, tokenCount, reasoningD | |||
| 2598 | return { timerValue, timerTitle }; | 2598 | return { timerValue, timerTitle }; |
| 2599 | } | 2599 | } |
| 2600 | 2600 | ||
| 2601 | export function scrollChatToBottom() { | 2601 | let requestId = null; |
| 2602 | if (power_user.auto_scroll_chat_to_bottom) { | 2602 | |
| 2603 | /** | ||
| 2604 | * Scrolls the chat to the bottom if configured to do so. | ||
| 2605 | * @param {object} [options] Options | ||
| 2606 | * @param {boolean} [options.waitForFrame] If true, waits for the animation frame before scrolling | ||
| 2607 | */ | ||
| 2608 | export function scrollChatToBottom({ waitForFrame } = {}) { | ||
| 2609 | if (!power_user.auto_scroll_chat_to_bottom) { | ||
| 2610 | return; | ||
| 2611 | } | ||
| 2612 | |||
| 2613 | const doScroll = () => { | ||
| 2603 | let position = chatElement[0].scrollHeight; | 2614 | let position = chatElement[0].scrollHeight; |
| 2604 | 2615 | ||
| 2605 | if (power_user.waifuMode) { | 2616 | if (power_user.waifuMode) { |
| @@ -2611,7 +2622,23 @@ export function scrollChatToBottom() { | |||
| 2611 | } | 2622 | } |
| 2612 | 2623 | ||
| 2613 | chatElement.scrollTop(position); | 2624 | chatElement.scrollTop(position); |
| 2625 | requestId = null; | ||
| 2626 | }; | ||
| 2627 | |||
| 2628 | // Do not check truthiness. requestId can loop to zero. | ||
| 2629 | if (requestId !== null) { | ||
| 2630 | cancelAnimationFrame(requestId); | ||
| 2614 | } | 2631 | } |
| 2632 | |||
| 2633 | if (!waitForFrame) { | ||
| 2634 | doScroll(); | ||
| 2635 | return; | ||
| 2636 | } | ||
| 2637 | |||
| 2638 | // This prevents layout thrashing. | ||
| 2639 | // https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value | ||
| 2640 | // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#file-what-forces-layout-md | ||
| 2641 | requestId = requestAnimationFrame(() => doScroll()); | ||
| 2615 | } | 2642 | } |
| 2616 | 2643 | ||
| 2617 | /** | 2644 | /** |
| @@ -3273,7 +3300,7 @@ class StreamingProcessor { | |||
| 3273 | this.markUIGenStarted(); | 3300 | this.markUIGenStarted(); |
| 3274 | } | 3301 | } |
| 3275 | hideSwipeButtons({ hideCounters: true }); | 3302 | hideSwipeButtons({ hideCounters: true }); |
| 3276 | scrollChatToBottom(); | 3303 | scrollChatToBottom({ waitForFrame: true }); |
| 3277 | return messageId; | 3304 | return messageId; |
| 3278 | } | 3305 | } |
| 3279 | 3306 | ||
| @@ -3376,7 +3403,7 @@ class StreamingProcessor { | |||
| 3376 | } | 3403 | } |
| 3377 | 3404 | ||
| 3378 | if (!scrollLock) { | 3405 | if (!scrollLock) { |
| 3379 | scrollChatToBottom(); | 3406 | scrollChatToBottom({ waitForFrame: true }); |
| 3380 | } | 3407 | } |
| 3381 | } | 3408 | } |
| 3382 | 3409 | ||