当父QFrame调整大小时,PyQt5的 QWebEngineView会闪烁,如何避免?

前端开发 2026-07-12

我有一个PyQt5应用,在一个 QFrame 内只有一个 QWebEngineView。框架起初很小(150×150),按下按钮时我将其调整到更大的尺寸(300×300)。我希望网页内容能够随框架一起缩放。

以下是一个最小可复现的示例:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QFrame
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import Qt

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(600, 500)
        self.is_big = False

        layout = QVBoxLayout(self)
        layout.setAlignment(Qt.AlignCenter)

        self.box = QFrame()
        self.box.setStyleSheet("border: 2px solid black;")
        self.box.setFixedSize(150, 150)
        self.box_layout = QVBoxLayout(self.box)
        self.box_layout.setContentsMargins(0, 0, 0, 0)

        self.web_widget = QWebEngineView()
        self.web_widget.setAttribute(Qt.WA_TranslucentBackground, True)
        self.web_widget.setStyleSheet("background: transparent;")
        self.web_widget.setHtml("""
            <html>
                <body style="display:flex; align-items:center; justify-content:center; height:100%; margin:0;">
                    <h2>Hello World</h2>
                </body>
            </html>
        """)
        self.box_layout.addWidget(self.web_widget)

        self.button = QPushButton("Toggle Size")
        self.button.clicked.connect(self.toggle_size)

        layout.addWidget(self.box)
        layout.addWidget(self.button)

    def toggle_size(self):
        if self.is_big:
            self.box.setFixedSize(150, 150)
        else:
            self.box.setFixedSize(300, 300)
        self.is_big = not self.is_big

app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

问题:
点击按钮时,QWebEngineView 会在左上角短暂闪现,然后才正确地调整大小。

问题:
如何在父框架调整大小时防止 QWebEngineView 闪烁?在PyQt5中是否有更好的方法来平滑缩放内容并同时调整容器大小?

解决方案

这个问题源于底层Chromium引擎应用样式的方式并不理想。大体而言,它先加载HTML,再应用所有样式,这在文档完全加载之前经常会导致页面元素的丑陋重新排布(取决于样式的具体性质)。Qt6使用的最新版Chromium仍然表现出这种行为,因此似乎是一个基本问题,短期内不太可能改变。

到目前为止,我找到的唯一变通方法有点hacky,但似乎相当可靠。(如果有人知道更好的办法,我会很感兴趣地了解)。基本思路是在渲染阶段隐藏 html 元素,然后在短暂延迟后再显示它。因此,在你的示例中,将 toggle_size 的槽函数替换为以下代码:

def toggle_size(self):
    def callback():
        if self.is_big:
            self.box.setFixedSize(150, 150)
        else:
            self.box.setFixedSize(300, 300)
        self.is_big = not self.is_big
        self.web_widget.page().runJavaScript(
            'document.documentElement.style.display = "block"')
    self.web_widget.page().runJavaScript(
        'document.documentElement.style.display = "none"',
        lambda result: QTimer.singleShot(10, callback))

在Linux上,使用PyQt-5.15.11或 PyQt-6.10.2时对我有效——但我从未在其他平台上测试过,因此见仁见智。

类似的变通方法也可用于应用自定义样式表(以及/或需要在页面完全加载之前操作DOM的脚本)。也许实现这一点的最佳方式是先重写并缓存HTML源代码,使其document-element变成类似如下的结构:

<html style="display: none">

然后使用 QWebEngineScript 通过JavaScript的 DOM操作在取消隐藏document-element之前注入自定义样式表,并通过事件监听在取消隐藏document-element时再次显示它。所以,大致地说,像这样:

script = QWebEngineScript()
script.setName('css')
script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
script.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)
script.setRunsOnSubFrames(True)
script.setSourceCode(f"""
(function() {{
    var css = document.createElement('style')
    css.type = 'text/css'
    css.id = 'custom-css'
    document.head.appendChild(css)
    css.innerText = {MY_CUSTOM_CSS!r}
}})()
""")
webview.profile().scripts().insert(script)

script = QWebEngineScript()
script.setName('js')
script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
script.setRunsOnSubFrames(False)
script.setSourceCode("""
window.addEventListener('load', function(event) {
    document.documentElement.style.display = 'block'
})
""")
webview.profile().scripts().insert(script)
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章