修正 Elementor 導覽選單那一下「閃」:實測穩定解法分享

在使用 Elementor 的導覽選單製作網站頁首(Header)時,常會遇到一個令人在意的細節:網頁剛載入時,選單項目會突然「跳動」一下,然後才看到下拉箭頭出現。

這種畫面上的抖動不只是視覺干擾,它其實會讓使用者在第一眼就感覺到畫面的不穩定與不一致。雖然它不構成錯誤,也不會破壞功能,但在體驗上卻足以讓整體介面顯得不夠扎實。

特別是當網站啟用了像 WP Rocket、LiteSpeed Cache 等快取外掛時,問題會變得更加明顯。這些外掛通常會調整 JavaScript 的載入時機,或延後腳本執行,進而放大了這種「畫面先顯示、箭頭後補上」的時間差。

問題根源:下拉箭頭是由 JS 動態插入

Elementor 的 Nav Menu 元件,在前端初始化時會掃描所有有子選單的項目,然後用 JavaScript 動態插入箭頭圖示。這個插入動作通常是在網頁主體載入完成後進行,也就是說——HTML 一開始是不含這些箭頭的。

具體來說,Elementor 是在 element_ready 階段插入 <span class="sub-arrow">,這動作不大,但正好會觸發瀏覽器對 DOM 結構的重新排版(reflow)與重繪(repaint)。因此你會看到畫面明明已經排好,卻因為多了一個元素導致整列選單發生閃爍或跳位現象。

這種「先載入再插入」的策略也會與 CSS 的排版邏輯產生衝突,尤其你若使用的是 flexbox、inline-block 排版方式,或設計上使用了自訂的間距、字級、行高等控制,這個箭頭插入的延遲就會非常明顯,甚至影響整體版面穩定性。

解決方向:讓箭頭預先存在、並隱藏插入的箭頭

既然問題在於JS插入的箭頭出現得太晚,我們就反過來處理:讓箭頭隱藏起來。

更精確的說法是:

  1. 讓 Elementor 的箭頭在伺服器端(PHP)渲染階段就寫入 HTML。
  2. 同時隱藏JS插入的箭頭,讓視覺上不會因為晚插入的箭頭產生偏移閃爍。

這樣做可以確保兩件事:

  • 畫面第一次載入時,箭頭就已經在 DOM 結構中,就算因為新增節點而重繪,也早一先一步使用css將其隱藏。
  • Elementor 的其他功能(例如子選單展開收合)依然可以正常使用,因為我們只是隱藏了原本閃爍的箭頭,並沒有移除整體互動行為。

程式碼實作

這段程式碼可以直接放進主題的 functions.php,或打包成自訂外掛使用。

不過更建議的做法是,建立一個子佈景主題(Child Theme),將這類客製化功能集中管理。這樣不僅能讓代碼結構更清晰,也能避免日後主題更新時自訂修改被覆蓋或消失的情況。

/**
 * 修復 Elementor 導航選單(Nav Menu)下拉指標閃爍問題
 * ------------------------------------------------------------
 * - 攔截 Nav Menu widget 的 HTML 輸出
 * - 插入並修正子選單箭頭圖示位置與樣式
 * - 修正 SVG 顏色繼承、避免閃爍或重疊
 * - 僅前台生效(不影響後台與 Elementor 編輯器)
 */

add_filter('elementor/widget/render_content', function ($content, $widget) {
    if (is_admin() || (defined('ELEMENTOR_EDITOR') && ELEMENTOR_EDITOR)) {
        return $content;
    }
    if (!did_action('elementor/loaded')) {
        return $content;
    }
    if (!class_exists('\Elementor\Icons_Manager')) {
        return $content;
    }
    if ('nav-menu' !== $widget->get_name()) {
        return $content;
    }
    if (strpos($content, 'menu-item-has-children') === false) {
        return $content;
    }

    $icon = $widget->get_settings_for_display('submenu_icon');
    $icon_html = '';

    if (!empty($icon)) {
        if (is_array($icon)) {
            ob_start();
            \Elementor\Icons_Manager::render_icon($icon, ['aria-hidden' => 'true']);
            $rendered = ob_get_clean();

            $rendered = preg_replace('/fill="[^"]*"/i', '', $rendered);
            $rendered = preg_replace('/<svg(.*?)>/i', '<svg$1 fill="currentColor">', $rendered);

            $icon_html = '<span class="sub-arrow sub-arrow-icon">' . $rendered . '</span>';
        } elseif (is_string($icon)) {
            $icon_html = '<span class="sub-arrow sub-arrow-icon"><i class="' . esc_attr($icon) . '"></i></span>';
        }
    }

    if ($icon_html) {
        $content = preg_replace(
            '#(<li[^>]*class="[^"]*\bmenu-item-has-children\b[^"]*"[^>]*>\s*<a[^>]*?>)(.*?)(</a>)#si',
            '$1$2 ' . $icon_html . '$3',
            $content
        );
    }

    static $css_printed = false;
    if (!$css_printed) {
        add_action('wp_footer', function () {
            echo '<style id="pongo-navmenu-icon-css">
                .elementor-nav-menu .sub-arrow { display: none !important; }
                .elementor-nav-menu .sub-arrow.sub-arrow-icon {
                    display: inline-block !important;
                    vertical-align: middle;
                    color: inherit !important;
                    fill: currentColor !important;
                }
                .elementor-nav-menu .menu-item-has-children:hover .sub-arrow-icon,
                .elementor-nav-menu .menu-item-has-children:focus-within .sub-arrow-icon {
                    color: currentColor !important;
                    fill: currentColor !important;
                }
            </style>';
        });
        $css_printed = true;
    }

    return $content;
}, 10, 2);Code language: PHP (php)

完成上述修改後,網頁載入的穩定性會有明顯改善。最直接的體感是:導覽選單不再抖動,箭頭從一開始就存在於版面中,整體畫面不再因 DOM 被修改而重新排版,也不會因箭頭晚一步出現而產生錯位、重疊、或 hover 效果異常的問題。

此外,這個方法是使用 Elementor 官方所提供的過濾器來增加輸出一個箭頭,不修改 Elementor 核心、不破壞編輯器預覽,對未來的更新也保有良好相容性。

結語

Elementor 之所以選擇在載入後再插入箭頭圖示,是出於元件化架構與樣式彈性的考量。然而在實際運行中,這種設計卻為導覽列帶來了意料之外的副作用——畫面閃爍、位移、整體穩定性下降。

如果你也遇到這個情況,其實不用再懷疑是 CSS 排版出了問題,也不必為了避開它而關閉快取或調整 JS 延遲策略。只要幾行簡潔的程式,就能讓導覽列在載入時維持穩定、不跳、不抖,呈現出應有的設計品質與細節一致性。

常見問答

本區問答由 AI 依文章內容自動整理,僅供快速參考,正式內容仍以全文為準。

Elementor 導覽選單載入時為什麼會「閃」一下?

這個閃爍來自 Elementor 的 Nav Menu 元件設計。
它的下拉箭頭並不是一開始就寫在 HTML 裡,而是在前端初始化的 element_ready 階段才透過 JavaScript 動態插入 sub-arrow 圖示。
這個插入動作雖然不大,卻會觸發瀏覽器對 DOM 的 reflow 與 repaint,因此你會看到畫面已經排好、卻又因為多了一個元素而瞬間跳位。
簡單說,是「先載入再插入」的策略與 CSS 排版邏輯產生衝突的結果。

這個閃爍跟 WP Rocket、LiteSpeed Cache 等快取外掛有關嗎?

有關,而且會被放大。
一般情況下這個跳動已經存在,但當你啟用 WP Rocket、LiteSpeed Cache 這類快取外掛時,它們通常會調整 JavaScript 的載入時機或延後腳本執行,使得「畫面先顯示、箭頭後補上」的時間差被拉得更明顯。
所以你如果發現裝了快取後選單反而更會抖,並不是快取外掛壞掉,而是它把原本隱藏的時序問題暴露出來。
解法是處理箭頭出現的時機,而不是回頭去關快取。

解決這個閃爍的核心思路是什麼?跟調 CSS 排版有什麼不同?

核心思路是反向處理:讓箭頭在伺服器端 PHP 渲染階段就先寫進 HTML,並用 CSS 隱藏 Elementor 後來才用 JS 插入的那一個。
這樣畫面第一次載入時箭頭就已存在於 DOM,不會因為晚補上一個節點而重繪位移。
這跟單純改 CSS 排版完全不同,問題的根因是 DOM 結構在載入後被改動,而不是樣式寫得不夠細,所以再怎麼調 flex、間距、行高,只要箭頭是後插入的,就還是會抖。

在哪些情境下這個閃爍特別需要修?只是視覺問題真的有差嗎?

雖然它不構成功能錯誤,但會直接影響使用者對網站的第一印象。
特別是當你使用 flexbox、inline-block 排版,或設計上有自訂的間距、字級、行高控制時,箭頭插入的延遲會非常明顯,甚至造成 hover 錯位、子選單異常。
另一個情境是品牌或形象網站,頁首是訪客第一眼接觸的區塊,畫面不穩會讓整體介面顯得不夠扎實。
如果你的網站重視細節一致性或剛裝了快取讓抖動更明顯,就值得處理。

這段修正程式碼要放哪裡?會不會影響 Elementor 之後更新?

建議建立子佈景主題(Child Theme),把這類客製化集中管理,避免主題更新時被覆蓋,也比直接寫進 functions.php 更乾淨。
打包成自訂外掛也是可行做法。
相容性方面不需要擔心,這個解法是透過 Elementor 官方提供的 elementor/widget/render_content 過濾器在 PHP 階段加入箭頭,並沒有修改 Elementor 核心、不影響編輯器預覽,子選單展開收合等互動行為也保留正常,對未來版本更新友善。