diff --git a/vite-tailwindcss/index.html b/vite-tailwindcss/index.html
index 5b385d0..9e50784 100644
--- a/vite-tailwindcss/index.html
+++ b/vite-tailwindcss/index.html
@@ -11,6 +11,8 @@
+
+
💕李琦❤️李逸烁 💒婚礼请柬
diff --git a/vite-tailwindcss/src/components/PhotoGallery.vue b/vite-tailwindcss/src/components/PhotoGallery.vue
index 58d3f2a..1503f92 100644
--- a/vite-tailwindcss/src/components/PhotoGallery.vue
+++ b/vite-tailwindcss/src/components/PhotoGallery.vue
@@ -238,6 +238,34 @@ const preloadNineGridImages = () => {
}
}
+/** 安卓专项:把当前 page 的所有图都预热(不创建 DOM,仅触发浏览器缓存下载)
+ * 让浏览器在空闲时下载+解码,避免滑到时才下载造成卡顿。
+ * iOS 不预热,保留原串行体验。 */
+const isAndroidClient = (() => {
+ if (typeof navigator === 'undefined') return false
+ const ua = navigator.userAgent || ''
+ return /Android/i.test(ua) && !/CrOS/i.test(ua)
+})()
+let androidPreloadStarted = false
+const preloadAllForAndroid = () => {
+ if (!isAndroidClient || androidPreloadStarted) return
+ const urls = [
+ props.page?.img1,
+ props.page?.img2,
+ ...(props.page?.images || []),
+ ].filter(Boolean)
+ if (!urls.length) return
+ androidPreloadStarted = true
+ // 错开下载,避免同时抢占首屏网络
+ urls.forEach((url, i) => {
+ setTimeout(() => {
+ const img = new Image()
+ img.decoding = 'async'
+ img.src = url
+ }, 800 + i * 120)
+ })
+}
+
const runNineSequential = async () => {
const total = (props.page.images || []).length
const { cancelled } = await seq.run({
@@ -378,6 +406,7 @@ watch(
onMounted(() => {
if (!props.nineGridAnimate) preloadNineGridImages()
+ preloadAllForAndroid()
if (SEQUENTIAL_SELF_TYPES.has(props.page?.type)) bindMultiObserver()
})
diff --git a/vite-tailwindcss/src/composables/useAndroidOptimize.js b/vite-tailwindcss/src/composables/useAndroidOptimize.js
new file mode 100644
index 0000000..c8e9033
--- /dev/null
+++ b/vite-tailwindcss/src/composables/useAndroidOptimize.js
@@ -0,0 +1,71 @@
+/**
+ * 安卓专项优化(iOS 保持原逻辑)
+ *
+ * - 检测是否为安卓
+ * - 提供图片 URL 改写工具:把阿里云 OSS 图压缩为 750px 宽 + webp
+ * 仅对安卓生效,iOS 原样返回,确保 iOS 视觉体验不变
+ */
+
+const isAndroidClient = (() => {
+ if (typeof navigator === 'undefined') return false
+ const ua = navigator.userAgent || ''
+ // 排除 ChromeOS / Android 的 CrOS 伪装;保留真正的 Android 移动端
+ return /Android/i.test(ua) && !/CrOS/i.test(ua)
+})()
+
+export const isAndroid = isAndroidClient
+
+const OSS_HOST = 'aliyuncs.com'
+const PROC_750 = 'image/resize,w_750,lfit/format,webp/q,85'
+
+/**
+ * 判断 URL 是否为阿里云 OSS 图(仅处理 OSS 图,不影响其它域/本地/blob URL)
+ */
+const isOssImage = (url) => {
+ if (typeof url !== 'string') return false
+ const u = url.trim()
+ if (!u || u.startsWith('data:') || u.startsWith('blob:')) return false
+ // 仅 http/https
+ if (!/^https?:\/\//i.test(u)) return false
+ // 排除图标/iconify 等非图片
+ if (/iconify\.design|\.svg(\?|$)/i.test(u)) return false
+ return u.includes(OSS_HOST)
+}
+
+/**
+ * 对单张 OSS 图片 URL 追加 OSS 处理参数(仅安卓)
+ * - 已存在 x-oss-process 时合并/覆盖 resize 与 format
+ * - 不改变非 OSS URL
+ *
+ * 注意:默认禁用(如下一行 return)。如需启用,注释掉该行。
+ * 之前出现"图片加载不出来",是因为部分 OSS 桶未开启图片处理服务 /
+ * 或 URL 已是 webp,再叠加 format,webp 在某些 bucket 配置下返回 400。
+ * 启用前请确认你的 OSS 已开通 "图片处理" 服务且支持 x-oss-process。
+ */
+export const optimizeImageUrl = (url) => {
+ return url
+ /* eslint-disable no-unreachable */
+ if (!isAndroidClient) return url
+ if (!isOssImage(url)) return url
+ const u = url.trim()
+ // 已有 x-oss-process:替换其中 resize / format
+ const qIdx = u.indexOf('?x-oss-process=')
+ if (qIdx >= 0) {
+ const before = u.slice(0, qIdx)
+ let proc = u.slice(qIdx + '?x-oss-process='.length)
+ proc = proc
+ .split('/')
+ .filter((seg) => !seg.startsWith('resize') && !seg.startsWith('format') && !seg.startsWith('q'))
+ .join('/')
+ const merged = proc ? `${PROC_750}/${proc}` : PROC_750
+ return `${before}?x-oss-process=${merged}`
+ }
+ return `${u}?x-oss-process=${PROC_750}`
+ /* eslint-enable no-unreachable */
+}
+
+/**
+ * 批量优化(供 normalizeImages 等使用)
+ */
+export const optimizeImageUrls = (urls = []) =>
+ urls.map((u) => optimizeImageUrl(u))
diff --git a/vite-tailwindcss/src/views/index/index.vue b/vite-tailwindcss/src/views/index/index.vue
index 8739fc4..d8fb7cb 100644
--- a/vite-tailwindcss/src/views/index/index.vue
+++ b/vite-tailwindcss/src/views/index/index.vue
@@ -271,7 +271,7 @@
-
![封面]()
+
@@ -613,7 +613,7 @@
@@ -628,6 +628,7 @@ import { toast } from '@/composables/useToast'
import { pickMusicUrl, rememberMusicPick } from '@/utils/musicPick'
import { getClientId } from '@/utils/clientId'
import { sendVisitBeacon } from '@/utils/visitBeacon'
+import { optimizeImageUrl } from '@/composables/useAndroidOptimize'
import { DANMAKU_COLORS, DANMAKU_GRADIENT_COLORS, DEFAULT_DANMAKU_COLOR, BLESSING_STYLES } from '@/utils/danmakuColors'
import { parseLrc, formatAudioTime, findLrcIndex } from '@/utils/parseLrc'
import CanvasEffects from '@/components/CanvasEffects.vue'
@@ -818,8 +819,8 @@ const firstScreenDuration = computed(() => {
return v >= 0 && v <= 30000 ? v : 4500
})
-const curtainBg = computed(() => ({ backgroundImage: `url(${data.value.heroImg})` }))
-const endImage = computed(() => data.value.endImg || data.value.heroImg)
+const curtainBg = computed(() => ({ backgroundImage: `url(${optimizeImageUrl(data.value.heroImg)})` }))
+const endImage = computed(() => optimizeImageUrl(data.value.endImg || data.value.heroImg))
const introExitClass = computed(() => {
const effect = ['wind', 'book', 'lift', 'doors'].includes(data.value.introExitEffect) ? data.value.introExitEffect : 'wind'
return `intro-exit-${effect}`
@@ -871,7 +872,7 @@ const normalizeImages = (images = []) => {
const seen = new Set()
return images
.filter((src) => typeof src === 'string' && src.trim())
- .map((src) => src.trim())
+ .map((src) => optimizeImageUrl(src.trim()))
.filter((src) => {
if (seen.has(src)) return false
seen.add(src)
@@ -1318,13 +1319,28 @@ onMounted(async () => {
loaded.formalPageHeight = loaded.formalPageHeight || DEFAULTS.formalPageHeight
loaded.calendarDate = loaded.calendarDate || DEFAULTS.calendarDate
loaded.introExitEffect = loaded.introExitEffect || DEFAULTS.introExitEffect
+ // 安卓专项:OSS 图统一压缩为 750px webp(iOS 不受影响)
+ if (loaded.heroImg) loaded.heroImg = optimizeImageUrl(loaded.heroImg)
+ if (loaded.endImg) loaded.endImg = optimizeImageUrl(loaded.endImg)
+ if (Array.isArray(loaded.photoPages)) {
+ loaded.photoPages = loaded.photoPages.map((p) => {
+ if (!p) return p
+ const next = { ...p }
+ if (next.img1) next.img1 = optimizeImageUrl(next.img1)
+ if (next.img2) next.img2 = optimizeImageUrl(next.img2)
+ if (Array.isArray(next.images)) next.images = next.images.map((u) => optimizeImageUrl(u))
+ return next
+ })
+ }
data.value = { ...data.value, ...DEFAULTS, ...loaded }
}
} catch (e) { /* 使用默认配置 */ }
syncPageScrollMode()
albumLoaded.value = false
- albumPreviewImages.value = localAlbumImages()
+ // 安卓专项:不在首屏预挂载整本相册,避免 全量创建导致主线程卡顿
+ // 改为用户首次点开预览时再加载(loadAlbumPreviewImages 内部有缓存保护)
+ albumPreviewImages.value = []
loadLikeStatus()
sendVisitBeacon()