提交 49dfc88f authored 作者: 龙菲's avatar 龙菲

增加懒加载

上级 5c9fde46
......@@ -12,8 +12,13 @@
'odd': !page.isWide && index % 2 !== 0,
'even': !page.isWide && index % 2 === 1
}]">
<img :src="page.src" :alt="`第 ${page.page_num} 页`" @error="handleImageError(index)"
:class="['page-image', { 'wide-image': page.isWide }]" />
<img
:data-page-index="index"
:src="page.src"
:alt="`第 ${page.page_num} 页`"
@error="handleImageError(index)"
:class="['page-image', { 'wide-image': page.isWide }]"
/>
<!-- 添加小图叠加层 -->
<div v-if="page.images && page.images.length > 0" class="small-images-overlay">
......@@ -82,84 +87,111 @@ const loading = ref(true)
const showExitMessage = ref(false)
const zoomLevel = ref(1)
const isInitialized = ref(false)
const imageMetadata = ref([])
const imageObserver = ref(null)
const loadingQueue = ref(new Set())
const maxConcurrentLoads = 5
const imageCache = ref(new Map())
const loadingTimeouts = ref(new Map())
// 修改预览相关的状态
const showViewer = ref(false)
const currentImageIndex = ref(0)
const previewImages = ref([])
// 处理页面数据,移除宽图处理
const processedPages = computed(() => {
return props.pages.map((item, index) => ({
src: item.page_url,
// 将 processedPages 改为普通数据属性
const processedPages = ref([])
// 修改处理页面的函数,只处理必要的属性
const processPages = (pages) => {
return pages.map((item, index) => ({
src: '', // 初始不设置src
originalIndex: index,
page_num: item.page_num, // 添加页码
page_num: item.page_num,
images: item.images || [],
isWide: item.isWide
isWide: false, // 初始设置为false
isLoaded: false,
url: item.page_url // 保存原始URL
}))
})
}
const checkImageDimensions = async (src) => {
return new Promise((resolve) => {
// 修改图片加载函数
const loadImage = async (pageIndex) => {
const page = processedPages.value[pageIndex]
if (!page || page.isLoaded) return true
const src = page.url
if (imageCache.value.has(src)) {
page.isLoaded = true
page.src = src
return true
}
if (loadingQueue.value.size >= maxConcurrentLoads) {
await new Promise(resolve => setTimeout(resolve, 50))
return loadImage(pageIndex)
}
loadingQueue.value.add(pageIndex)
const timeoutId = setTimeout(() => {
loadingQueue.value.delete(pageIndex)
loadingTimeouts.value.delete(pageIndex)
}, 5000)
loadingTimeouts.value.set(pageIndex, timeoutId)
try {
const img = new Image()
await new Promise((resolve, reject) => {
img.onload = () => {
// 如果宽高比大于 1.8,认为是宽图
const isWide = (img.width / img.height) > 1.8
resolve({
width: img.width,
height: img.height,
isWide
})
clearTimeout(timeoutId)
loadingTimeouts.value.delete(pageIndex)
imageCache.value.set(src, true)
page.isLoaded = true
page.src = src
resolve()
}
img.onerror = () => {
resolve({ width: 0, height: 0, isWide: false })
clearTimeout(timeoutId)
loadingTimeouts.value.delete(pageIndex)
reject(new Error('Image load failed'))
}
img.src = src
})
}
const destroyTurn = () => {
if (magazine.value && isInitialized.value) {
try {
const $magazine = $(magazine.value)
// 移除所有 turn.js 相关的事件和数据
$magazine.off('.turn')
$magazine.removeData('turn')
isInitialized.value = false
return true
} catch (error) {
console.error('Error destroying turn.js:', error)
}
return false
} finally {
loadingQueue.value.delete(pageIndex)
}
}
const loadVisiblePages = (currentPage) => {
// 修改加载可见页面的函数
const loadVisiblePages = async (currentPage) => {
if (!magazine.value) return
const $magazine = $(magazine.value)
const totalPages = $magazine.turn('pages')
// 计算需要加载的页面范围(当前页和前后各两页)
const startPage = Math.max(1, currentPage - 2)
const endPage = Math.min(totalPages, currentPage + 2)
// 只加载当前页和下一页
const startPage = currentPage
const endPage = Math.min(totalPages, currentPage + 1)
// 加载范围内的页面
const loadTasks = []
for (let i = startPage; i <= endPage; i++) {
const $page = $magazine.find(`.p${i}`)
const $img = $page.find('img')
if ($img.length && !$img[0].complete) {
// 使用新的 Image 对象预加载
const img = new Image()
img.onload = () => {
$img[0].src = img.src
const pageIndex = i - 1
if (pageIndex >= 0 && pageIndex < processedPages.value.length && !processedPages.value[pageIndex].isLoaded) {
loadTasks.push(loadImage(pageIndex))
}
img.src = $img[0].src
}
try {
await Promise.all(loadTasks)
} catch (error) {
console.error('加载页面失败:', error)
}
}
// 修改初始化函数
const initBook = async () => {
if (!magazine.value || !processedPages.value.length) return
......@@ -171,13 +203,11 @@ const initBook = async () => {
const pageHeight = Math.min(800, viewportHeight - 100)
await nextTick()
await new Promise(resolve => setTimeout(resolve, 100))
try {
const $magazine = $(magazine.value)
const $viewport = $('.magazine-viewport')
// 确保视口样式固定
$viewport.css({
'overflow': 'hidden',
'position': 'relative',
......@@ -185,7 +215,6 @@ const initBook = async () => {
'height': '100%'
})
// 设置页面样式
$magazine.children().each(function () {
const $page = $(this)
$page.css({
......@@ -195,7 +224,6 @@ const initBook = async () => {
height: pageHeight
})
// 设置图片样式
const $img = $page.find('img')
if ($img.length) {
$img.css({
......@@ -206,7 +234,6 @@ const initBook = async () => {
}
})
// 初始化 turn.js
$magazine.turn({
width: pageWidth * 2,
height: pageHeight,
......@@ -218,44 +245,28 @@ const initBook = async () => {
autoCenter: true,
page: 1,
when: {
turning: (event, page, view) => {
turning: async (event, page, view) => {
loading.value = true
loadVisiblePages(page)
await loadVisiblePages(page)
},
turned: (event, page, view) => {
turned: async (event, page, view) => {
await loadVisiblePages(page)
loading.value = false
loadVisiblePages(page)
}
}
})
isInitialized.value = true
// 初始加载前几页
loadVisiblePages(1)
await loadVisiblePages(1)
} catch (error) {
console.error('Turn.js initialization error:', error)
}
}
// 修改加载图片函数
const loadImages = async () => {
loading.value = true
try {
// 获取所有图片的尺寸信息
const metadataPromises = props.pages.map(item => checkImageDimensions(item.page_url))
imageMetadata.value = await Promise.all(metadataPromises)
// 等待所有图片加载完成
await Promise.all(props.pages.map(item => {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = resolve
img.onerror = reject
img.src = item.page_url
})
}))
await initBook()
} catch (error) {
console.error('加载图片失败:', error)
......@@ -264,8 +275,100 @@ const loadImages = async () => {
}
}
const handleImageError = (index) => {
console.error(`图片加载失败: ${props.pages[index].page_url}`)
// 监听 props.pages 的变化
watch(() => props.pages, (newPages) => {
if (newPages && newPages.length > 0) {
processedPages.value = processPages(newPages)
loadImages()
}
}, { immediate: true })
const checkImageDimensions = async (src) => {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
// 如果宽高比大于 1.8,认为是宽图
const isWide = (img.width / img.height) > 1.8
resolve({
width: img.width,
height: img.height,
isWide
})
}
img.onerror = () => {
resolve({ width: 0, height: 0, isWide: false })
}
img.src = src
})
}
const destroyTurn = () => {
if (magazine.value && isInitialized.value) {
try {
const $magazine = $(magazine.value)
// 移除所有 turn.js 相关的事件和数据
$magazine.off('.turn')
$magazine.removeData('turn')
isInitialized.value = false
} catch (error) {
console.error('Error destroying turn.js:', error)
}
}
}
// 初始化 Intersection Observer
const initImageObserver = () => {
imageObserver.value = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
const pageIndex = parseInt(img.dataset.pageIndex)
if (!processedPages.value[pageIndex].isLoaded) {
img.src = processedPages.value[pageIndex].src
processedPages.value[pageIndex].isLoaded = true
}
}
})
}, {
root: null,
rootMargin: '50px',
threshold: 0.1
})
}
// 修改图片错误处理函数
const handleImageError = async (index) => {
const src = processedPages.value[index].src
// 清除超时
if (loadingTimeouts.value.has(index)) {
clearTimeout(loadingTimeouts.value.get(index))
loadingTimeouts.value.delete(index)
}
// 尝试重新加载
const retryCount = 3
for (let i = 0; i < retryCount; i++) {
try {
const success = await loadImage(index)
if (success) {
return
}
} catch (error) {
// 忽略错误
}
// 递增重试延迟
const delay = 1000 * (i + 1)
await new Promise(resolve => setTimeout(resolve, delay))
}
// 如果所有重试都失败,使用占位图
const $img = $(magazine.value).find(`.p${index + 1} img`)
if ($img.length) {
$img[0].src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2YwZjBmMCIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTYiIGZpbGw9IiM5OTkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7lvIDlp4vmlbDmja7lupM8L3RleHQ+PC9zdmc+'
}
}
const next = () => {
......@@ -477,18 +580,20 @@ onMounted(async () => {
})
onUnmounted(() => {
// 清除所有超时
loadingTimeouts.value.forEach(timeoutId => clearTimeout(timeoutId))
loadingTimeouts.value.clear()
imageCache.value.clear()
loadingQueue.value.clear()
if (imageObserver.value) {
imageObserver.value.disconnect()
}
destroyTurn()
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('resize', handleResize)
})
// 监听页面数据变化
watch(() => props.pages, async (newPages) => {
if (newPages && newPages.length > 0) {
await loadImages()
}
}, { deep: true })
// 修改小图点击事件处理函数
const handleSmallImageClick = (smallImage, pageNum) => {
// 获取当前页面所有小图的URL
......
/*
* @Author: 龙菲 1373694886@qq.com
* @Date: 2025-04-23 22:37:01
* @LastEditors: 龙菲 1373694886@qq.com
* @LastEditTime: 2025-04-23 22:41:24
* @FilePath: \pic-reader\src\utils\request.js
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import axios from 'axios';
import { ElMessage } from 'element-plus';
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论