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 | 1452 | mediaLoaded++; |
| 1453 | 1453 | if (mediaLoaded === media.length) { |
| 1454 | 1454 | scrollChatToBottom({ waitForFrame: true }); |
| 1455 | 1455 | } |
| 1456 | 1456 | } |
| 1457 | 1457 | } |
| @@ -2598,8 +2598,19 @@ function formatGenerationTimer(gen_started, gen_finished, tokenCount, reasoningD | ||
| 2598 | 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 | 2614 | let position = chatElement[0].scrollHeight; |
| 2604 | 2615 | |
| 2605 | 2616 | if (power_user.waifuMode) { |
| @@ -2611,7 +2622,23 @@ export function scrollChatToBottom() { | ||
| 2611 | 2622 | } |
| 2612 | 2623 | |
| 2613 | 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); | |
| 2631 | + } | |
| 2632 | + | |
| 2633 | + if (!waitForFrame) { | |
| 2634 | + doScroll(); | |
| 2635 | + return; | |
| 2614 | 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 | 3300 | this.markUIGenStarted(); |
| 3274 | 3301 | } |
| 3275 | 3302 | hideSwipeButtons({ hideCounters: true }); |
| 3276 | 3303 | scrollChatToBottom({ waitForFrame: true }); |
| 3277 | 3304 | return messageId; |
| 3278 | 3305 | } |
| 3279 | 3306 | |
| @@ -3376,7 +3403,7 @@ class StreamingProcessor { | ||
| 3376 | 3403 | } |
| 3377 | 3404 | |
| 3378 | 3405 | if (!scrollLock) { |
| 3379 | 3406 | scrollChatToBottom({ waitForFrame: true }); |
| 3380 | 3407 | } |
| 3381 | 3408 | } |
| 3382 | 3409 | |