Skip to content

Live Preview: dragging the editor/preview resizer mostly leaves the iframe unscrollable in Chromium Browsers #14540

@avocadesign

Description

@avocadesign

Bug description

Dragging the Live Preview resize handle by a almost always leaves the preview iframe unscrollable until something forces a content re-render or a deep layout change (e.g. sometimes crossing a breakpoint, typing a character in a content field, switching responsive device so the preview re-renders).

Safari is unaffected.

Video of the issue in action on clean Statamic install
https://www.loom.com/share/a0cb8fd2522d478f9fe704e67366d852

I've reproduced this on clean Statamic installs and multiple client sites.

How to reproduce

  1. Fresh Statamic v6.14.0 site or a site built with default Peak starter (reproduces on at least 3 separate Peak installs — also on a stripped-down bare-HTML layout, confirming the bug is upstream of any frontend code).
  2. Open any entry's Live Preview.
  3. Drag the resize handle 1–2 pixels, (sometimes crossing breakpoint will allow the scroll to work, small drags are worse than big ones).
  4. Release the mouse.
  5. Try to scroll the preview iframe.

It can be worked around with a dirty hack in the live preview to basically have a transparent box that is always printing updates based on a text mutation on each resize - but clearly that's not ideal.

{{ if live_preview || get:live-preview }}
            {{# Chromium + Statamic Live Preview workaround. On a sub-breakpoint #}}
            {{# iframe width change, Chromium fails to invalidate the iframe's #}}
            {{# scroll state. A visible (in-viewport) element that receives a #}}
            {{# `.value` mutation on each resize forces enough repaint for scroll #}}
            {{# to survive. Off-screen / opacity-0 variants get optimized out by #}}
            {{# Chromium and don't work. Safari doesn't need this. #}}
            {{# Chromium + Statamic Live Preview drag-handle scroll workaround. #}}
            {{# #}}
            {{# Dragging the Live Preview resize handle by a pixel inside a #}}
            {{# single Tailwind breakpoint leaves Chromium's iframe scroll state #}}
            {{# stale — scroll becomes impossible until a content re-render or a #}}
            {{# breakpoint cross forces a deep reflow. Safari is unaffected. #}}
            {{# #}}
            {{# Two things are needed to resolve it, determined empirically: #}}
            {{#   1. An in-viewport textarea whose multi-line value grows on every #}}
            {{#      resize tick — Chromium's repaint of the textarea's internal #}}
            {{#      scrollbar is what actually unsticks the iframe's scroll. #}}
            {{#      Off-screen / opacity-0 / CSS-var variants did not work. #}}
            {{#   2. A synthetic mouseup dispatched on the parent window 150 ms #}}
            {{#      after the drag ends — Statamic's Resizer.vue sometimes leaves #}}
            {{#      `pointer-events-none` stuck on .live-preview-contents, which #}}
            {{#      inherits into the iframe and blocks pointer events. #}}
            {{# #}}
            {{# The visible green box top-right is unfortunate but load-bearing. #}}
            {{# Remove once fixed upstream in Statamic / Chromium. #}}
            <textarea id="lp-scrollfix-debug" readonly aria-hidden="true" tabindex="-1" style="position:fixed;top:8px;right:8px;z-index:2147483647;background:transparent;color:transparent;font:10px monospace;padding:2px 4px;border:none;width:60px;height:20px;resize:none;white-space:pre;overflow:hidden;pointer-events:none"></textarea>
            <script>
                (() => {
                    const panel = document.getElementById('lp-scrollfix-debug');
                    const lines = [];
                    const push = (line) => {
                        lines.push(new Date().toLocaleTimeString().slice(3) + ' ' + line);
                        if (lines.length > 40) lines.shift();
                        if (panel) panel.value = lines.join('\n');
                    };
                    push('loaded');

                    if (typeof ResizeObserver === 'undefined') { push('ERROR: no ResizeObserver'); return; }

                    let timer = null;
                    let roN = 0, cleanupN = 0;
                    const cleanup = () => {
                        timer = null;
                        cleanupN++;
                        try {
                            const parentWin = window.parent;
                            const container = parentWin.document.querySelector('.live-preview-contents');
                            const hasClass = !!container?.classList.contains('pointer-events-none');
                            push(`cleanup #${cleanupN} — stuck=${hasClass}`);
                            if (hasClass) {
                                parentWin.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }));
                                setTimeout(() => {
                                    const after = container.classList.contains('pointer-events-none');
                                    push(`  after mouseup: stuck=${after}`);
                                }, 50);
                            }
                        } catch (e) { push(`cleanup error: ${e.message}`); }
                    };
                    new ResizeObserver((entries) => {
                        roN++;
                        const r = entries[0].contentRect;
                        push(`RO#${roN} ${r.width.toFixed(0)}×${r.height.toFixed(0)}`);
                        if (timer) clearTimeout(timer);
                        timer = setTimeout(cleanup, 150);
                    }).observe(document.documentElement);
                    push('observer attached');
                })();
            </script>
        {{ /if }}

Logs

Environment

Environment
Laravel Version: 13.6.0
PHP Version: 8.4.13
Composer Version: 2.9.5
Environment: local
Debug Mode: ENABLED
Maintenance Mode: OFF
Timezone: UTC
Locale: en

Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED

Drivers
Broadcasting: log
Cache: file
Database: sqlite
Logs: stack / single
Mail: log
Queue: sync
Session: file

Storage
public/storage: NOT LINKED

Statamic
Addons: 0
Sites: 1
Stache Watcher: Enabled (auto)
Static Caching: Disabled
Version: 6.14.0 Solo

Installation

Fresh statamic/statamic site via CLI

Additional details

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions