背景问题

某博客内容过长,在移动端浏览时发现 backdrop-filter: blur 无法生效。一番排查后,这是因为浏览器在处理超大元素时,出于性能优化的考虑,可能不会渲染模糊效果。

示例错误代码:

vue
<template> <div class="content"> <p v-for="i in 1000" :key="i">内容 {{ i }}</p> </div> </template> <style> * { padding: 0; margin: 0; box-sizing: border-box; } body { background: url("https://api.xingzhige.com/API/Bing_img/"); background-position: center; background-repeat: no-repeat; background-size: cover; background-attachment: fixed; } .content { position: relative; backdrop-filter: blur(10px); background: rgba(255, 255, 255, 0.3); } </style>

尝试修复方案

方案 1:滚动监测动态渲染内容

思路:将内容分批渲染,初始只显示少量元素,随着滚动加载更多。
优点:模糊效果生效。
缺点:滚动时仍可能卡顿,高度过大时 blur 仍可能失效。

vue
<template> <div class="container"> <div v-for="item in visibleContent" :key="item">{{ item }}</div> </div> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from "vue"; const allContent = Array.from({ length: 1000 }, (_, i) => `内容 ${i + 1}`); const visibleContent = ref(allContent.slice(0, 20)); function onScroll() { const scrollBottom = window.scrollY + window.innerHeight; const docHeight = document.documentElement.scrollHeight; if (scrollBottom >= docHeight - 10) { // 接近底部触发 const nextCount = visibleContent.value.length + 20; visibleContent.value = allContent.slice(0, nextCount); } } onMounted(() => { onScroll(); window.addEventListener("scroll", onScroll); }); onBeforeUnmount(() => { window.removeEventListener("scroll", onScroll); }); </script> <style> * { padding: 0; margin: 0; box-sizing: border-box; } body { background: url("https://api.xingzhige.com/API/Bing_img/"); background-position: center; background-repeat: no-repeat; background-size: cover; background-attachment: fixed; } .container { background: rgba(255, 255, 255, 0.3); backdrop-filter: blur(10px); } </style>

方案 2:MutationObserver 监控内容变化

思路:使用哨兵元素和 MutationObserver 监控内容变化,动态增加内容。
实际效果

  • 滚动丝滑,但在使用 Vimium C 插件的 G 快捷键跳转到底部时失效。

  • 经过测试,这种方法在移动端仍然无法保证 backdrop-filter 稳定生效,高度过大时 blur 仍可能失效。

结论:此方案不可作为可靠解决方法。

vue
<template> <div ref="container" class="observer-container"> <div v-for="item in visibleContent" :key="item">{{ item }}</div> <div ref="sentinel"></div> </div> </template> <script setup> import { ref, onMounted } from 'vue' const allContent = Array.from({ length: 100 }, (_, i) => `内容 ${i + 1}`) const visibleContent = ref(allContent.slice(0, 20)) const container = ref(null) const sentinel = ref(null) onMounted(() => { const observer = new MutationObserver(() => { const nextCount = visibleContent.value.length + 20 visibleContent.value = allContent.slice(0, nextCount) }) if (sentinel.value) observer.observe(sentinel.value, { childList: true }) }) </script> <style scoped> .observer-container { width: 100%; overflow: visible; backdrop-filter: blur(10px); background: rgba(255, 255, 255, 0.3); } </style>

方案 3:固定模糊层

思路:在页面上方覆盖 fixed 模糊层,内容滚动时模糊层固定。
优点:模糊效果稳定。缺点:页面上方有额外内容时位置可能错位,布局调整麻烦。

vue
<template> <div class="fixed-blur-wrapper"> <div class="blur-layer"></div> <div class="content"> <p v-for="i in 1000" :key="i">内容 {{ i }}</p> </div> </div> </template> <style> * { padding: 0; margin: 0; box-sizing: border-box; } body { background: url("https://api.xingzhige.com/API/Bing_img/"); background-position: center; background-repeat: no-repeat; background-size: cover; background-attachment: fixed; } .fixed-blur-wrapper { position: relative; background: rgba(255, 255, 255, 0.3); } .blur-layer { position: fixed; inset: 0; width: 100%; height: 100vh; backdrop-filter: blur(10px); pointer-events: none; z-index: 0; } .content { position: relative; z-index: 1; }

方案 4(最终方案):sticky 模糊层

思路:通过 sticky 层将 blur 限制在可视区域,保证上方内容不受影响。
优点

  • 模糊效果在移动端稳定生效。

  • 支持页面顶部有额外内容,不影响整体布局。

备注:目前这是在移动端兼顾性能和布局的较优方案,但不排除未来可以用 WebGPU 或 CSS filter 替代方案进一步优化。

vue
<template> <div class="container"> <div class="blur-layer-container"> <div class="blur-layer"></div> </div> <p v-for="i in 1000" :key="i">内容 {{ i }}</p> </div> </template> <style> * { padding: 0; margin: 0; box-sizing: border-box; } body { background: url("https://api.xingzhige.com/API/Bing_img/"); background-position: center; background-repeat: no-repeat; background-size: cover; background-attachment: fixed; } .container { position: relative; background: rgba(255, 255, 255, 0.3); } .blur-layer-container { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: -1; } .blur-layer { position: sticky; top: 0; width: 100%; height: 100%; max-height: 100vh; backdrop-filter: blur(10px); backface-visibility: hidden; transform: translateZ(0); will-change: transform; } </style>
© 2026 Immortal's blog.