iOS 12浏览器中SPA动态加载HTML object部件随机不渲染问题求助
Hey there, let's break down why this random non-rendering quirk is hitting iOS 12 Safari/iOS Chrome, and walk through actionable fixes to resolve it.
Root Cause Analysis
The issue boils down to specific WebKit engine behavior in iOS 12, combined with your current load event and DOM insertion logic:
- Layout Reflow Timing Bug: iOS 12 Safari has known inconsistencies when applying styles to dynamically loaded external content (like
<object>-embedded HTML). When you split style application across two load event triggers (the early return + style tweak for Chrome/Safari), the browser often fails to trigger a proper layout update—leaving elements stuck at (0,0) visually even though DOM styles look correct. - Fragment Insertion Race Condition: Adding the
<object>to a DocumentFragment first before inserting it into the main DOM creates a timing conflict in iOS 12. The browser may start loading the widget's content before the fragment is attached to the live DOM, leading to incomplete layout calculations when the load event fires. - Double Load Event Workaround Conflict: Your workaround for Chrome/Safari's double load event fires a premature
position:absolutestyle change and exits early. On iOS 12, this throws off the layout engine, preventing it from properly applying the final position/width/height styles in the second load event.
Step-by-Step Fixes
1. Simplify Load Event Handling (Ditch the Premature Style Tweak)
Rewrite your widgetLoaded function to handle the double load event cleanly, without confusing iOS 12's layout engine:
function widgetLoaded(e) { var loadObj = e.target; if (!loadObj) return; // Handle Chrome's null target edge case var widgetName = loadObj.id; var myWidget = widgets[widgetName]; // Exit early if we've already processed this widget's load if (myWidget.loaded) return; myWidget.loaded = true; // Get reference to the widget's internal document var defView = loadObj.contentDocument?.defaultView; if (!defView) return; // Guard against missing content // Apply all styles in one go const styles = ` position:absolute; overflow:scroll; left:${myWidget.locX}px; top:${myWidget.locY}px; z-index:${zIndex}; width:${myWidget.scaleX * defView.options.settings.iniWidth}px; height:${myWidget.scaleY * defView.options.settings.iniHeight}px; `; loadObj.setAttribute("style", styles.replace(/\s+/g, ' ')); // Force iOS 12 to trigger a layout update loadObj.offsetHeight; // Reading offsetHeight forces the browser to recalculate layout }
2. Insert the Object Directly into the Live DOM
Skip the DocumentFragment and insert the <object> directly into your target container. This ensures the browser tracks the element's layout from the moment it's created:
function loadWidget(parentContainer, widgetName) { var widgetObj = document.createElement("object"); widgetObj.data = `widgets/${widgets[widgetName].type}.html`; widgetObj.className = "widget unselectable"; widgetObj.id = widgetName; widgetObj.name = widgetName; // Attach event listeners BEFORE inserting into DOM widgetObj.addEventListener("load", widgetLoaded, false); widgetObj.addEventListener("error", badLoad, true); // Insert directly into the target container parentContainer.appendChild(widgetObj); }
3. Tweak CSS for iOS 12 Compatibility
Update your .widget class to avoid layout conflicts specific to iOS 12:
.widget { display: block; /* Replace inline with block/inline-block to fix iOS layout quirks */ border: none; /* Remove default object border that can cause unexpected spacing */ }
4. Add an iOS 12-Specific Delay (Fallback)
If occasional issues persist, add a tiny delay before applying styles to give iOS 12 time to fully parse the widget's content:
function widgetLoaded(e) { // ... existing guard clauses and state tracking ... // Target iOS 12 specifically const isiOS12 = /iPhone OS 12_/.test(navigator.userAgent); const delay = isiOS12 ? 100 : 0; setTimeout(() => { // Apply styles and force reflow const styles = ` position:absolute; overflow:scroll; left:${myWidget.locX}px; top:${myWidget.locY}px; z-index:${zIndex}; width:${myWidget.scaleX * defView.options.settings.iniWidth}px; height:${myWidget.scaleY * defView.options.settings.iniHeight}px; `; loadObj.setAttribute("style", styles.replace(/\s+/g, ' ')); loadObj.offsetHeight; }, delay); }
Why This Works
- Single Load Event Processing: Tracking the
loadedflag eliminates the need for a premature style tweak, preventing confusion in iOS 12's layout engine. - Direct DOM Insertion: Attaching the
<object>to the live DOM immediately ensures the browser tracks its layout from the start, fixing race conditions. - Forced Reflow: Reading
offsetHeightafter applying styles forces iOS 12 to recalculate the element's position and render it correctly. - iOS 12 Delay: The small timeout gives the browser time to fully parse the widget's content before applying layout styles, resolving remaining timing gaps.
内容的提问来源于stack exchange,提问作者deandob

