本系列教程共十篇
效果预览
老规矩,先看效果
前置步骤
本效果基于valaxy内置的foucGuard服务,需要先开启,全名叫FOUC(Flash of Unstyled Content)防护配置。效果是,通过在 <head> 中内联 body { opacity: 0 !important } 隐藏页面,并通过 JS 监测所有样式表加载完成后,移除该隐藏样式标签以显示页面,防止首屏样式闪烁和样式分批渲染的问题。
简单来说就是在你的页面加载完成之前一直白屏,以防加载出乱码来,但是白屏不太美观,所以我加入了加载动画
先开启服务:
这是控制该组件的代码
enabled(默认 true):是否启用 FOUC 防护
maxDuration(默认 5000):最大等待时间(毫秒),作为 CSS 加载失败时的安全兜底。设为 0 可禁用超时兜底
启用方式如下:
修改valaxy.config.ts文件,在其中export default defineValaxyConfig({})部分增加以下代码:
1 2 3 4 5 6 7
| build: { ssgForPagination: false, foucGuard: { enabled: true, maxDuration: 5000, }, },
|
最终的效果是这样的,别加错了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { defineValaxyConfig } from 'valaxy'
export default defineValaxyConfig({ //... build: { ssgForPagination: false, foucGuard: { enabled: true, maxDuration: 5000, }, }, //... })
|
好啦,到这里服务就已经开起来,现在重启服务,刷新页面就能看到白屏了
开始美化
修改文件
以下部分是修改文件,不是新增哦~
文件valaxy.config.ts增加以下代码,下面是最终效果,注意这增加了两部分哦
1 2
| import { vaFoucLoader } from './plugins/va-fouc-loader' import siteConfig from './site.config'
|
上面是第一部分,下面是第二部分,都要添加哦,而且给出的是完整代码,注意不要重复哦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export default defineValaxyConfig({ //....
vite: { plugins: [vaFoucLoader({ avatar: siteConfig.author?.avatar, title: siteConfig.title, subtitle: siteConfig.subtitle, primary: ' })],
}, //..... })
|
新增文件
下面是新增文件部分,一共新增1个文件~
新增文件plugins\va-fouc-loader.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
| import type { Plugin } from 'vite'
export interface VaFoucLoaderOptions { avatar?: string title?: string subtitle?: string primary?: string }
function buildSiteName(options: VaFoucLoaderOptions) { const title = options.title?.trim() const subtitle = options.subtitle?.trim()
if (title && subtitle) return `${title}-${subtitle}`
return title || subtitle || '' }
const FOUC_STYLE = '<style id="valaxy-fouc">body{opacity:0!important}</style>'
function escapeHtml(value: string) { return value .replace(/&/g, '&') .replace(/"/g, '"') .replace(/</g, '<') .replace(/>/g, '>') }
function buildLoaderStyle(primary = '#E9CCCC') { const safePrimary = escapeHtml(primary)
return `<style id="va-fouc-loader-style"> #va-fouc-loader { position: fixed; inset: 0; z-index: 2147483646; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px; background: #fff; transition: opacity 0.28s ease, visibility 0.28s ease; }
html.dark #va-fouc-loader { background: #000; }
#va-fouc-loader.is-hidden { opacity: 0; visibility: hidden; pointer-events: none; }
#va-fouc-loader__spinner { position: relative; width: 108px; height: 108px; display: flex; align-items: center; justify-content: center; }
#va-fouc-loader__ring { position: absolute; inset: 0; border: 4px solid color-mix(in srgb, ${safePrimary} 22%, transparent); border-top-color: ${safePrimary}; border-radius: 50%; animation: va-fouc-spin 0.75s linear infinite; }
#va-fouc-loader__avatar { position: relative; z-index: 1; width: 92px; height: 92px; border-radius: 50%; object-fit: cover; background: color-mix(in srgb, ${safePrimary} 12%, transparent); }
#va-fouc-loader__dots { display: flex; gap: 6px; align-items: center; height: 8px; }
#va-fouc-loader__dots span { width: 6px; height: 6px; border-radius: 50%; background: ${safePrimary}; opacity: 0.35; animation: va-fouc-bounce 1.1s ease-in-out infinite; }
#va-fouc-loader__dots span:nth-child(2) { animation-delay: 0.15s; }
#va-fouc-loader__dots span:nth-child(3) { animation-delay: 0.3s; }
#va-fouc-loader__text { font-family: system-ui, -apple-system, "Segoe UI", sans-serif; font-size: 14px; letter-spacing: 0.08em; color: color-mix(in srgb, ${safePrimary} 88%, #666); user-select: none; }
@keyframes va-fouc-spin { to { transform: rotate(360deg); } }
@keyframes va-fouc-bounce { 0%, 80%, 100% { transform: translateY(0); opacity: 0.35; }
40% { transform: translateY(-5px); opacity: 1; } } </style>` }
function buildLoaderMarkup(options: VaFoucLoaderOptions) { const avatar = options.avatar ? `<img id="va-fouc-loader__avatar" src="${escapeHtml(options.avatar)}" alt="">` : ''
const text = escapeHtml(buildSiteName(options))
return `<div id="va-fouc-loader" aria-hidden="true"> <div id="va-fouc-loader__spinner"> <div id="va-fouc-loader__ring"></div> ${avatar} </div> <div id="va-fouc-loader__dots"><span></span><span></span><span></span></div> <div id="va-fouc-loader__text">${text}</div> </div>` }
const LOADER_SCRIPT = `<script> ;(function () { var HIDE_CLS = 'is-hidden' var LOADER_ID = 'va-fouc-loader' var hidden = false
function hideLoader() { if (hidden) return
hidden = true
var loader = document.getElementById(LOADER_ID)
if (!loader) return
loader.classList.add(HIDE_CLS)
setTimeout(function () { loader.remove()
var style = document.getElementById('va-fouc-loader-style')
if (style) style.remove() }, 300) }
function appReady() { var app = document.getElementById('app') return !!(app && app.childElementCount > 0) }
function tryHide() { if (document.readyState === 'complete' && appReady()) hideLoader() }
window.addEventListener('load', function () { tryHide()
if (!hidden) { var tries = 0
var timer = setInterval(function () { tries++ tryHide()
if (hidden || tries >= 120) clearInterval(timer) }, 50) } })
setTimeout(hideLoader, 8000) })() <\/script>`
export function vaFoucLoader(options: VaFoucLoaderOptions = {}): Plugin { const loaderStyle = buildLoaderStyle(options.primary) const loaderMarkup = buildLoaderMarkup(options) const loaderHead = `${loaderStyle}${LOADER_SCRIPT}`
return { name: 'va-fouc-loader',
transformIndexHtml: { order: 'post',
handler(html) { if (html.includes('id="va-fouc-loader"')) return html
let next = html
if (next.includes(FOUC_STYLE)) next = next.replace(FOUC_STYLE, `${FOUC_STYLE}${loaderHead}`) else next = next.replace('<head>', `<head>${loaderHead}`)
return next.replace('<body>', `<body>${loaderMarkup}`) }, }, } }
|
好啦,本次教程又结束啦