遇到了这么一个需求,需要在微信小程序中点击文件,进行文件预览。
要求:
- 图片:长图需要宽度100%高度自适应;横图的话宽度100%,高度居中显示
- 视频:视频不管横向还是竖向都居中显示,有全屏播放按钮
- pdf:pdf需要宽度100%,高度自适应。可以预览多页pdf文件。
概要:
使用原生html、js、css写的,通过url吧要预览的文件的url传递过去。
图片使用<img>进行预览,自己写样式
视频使用的是video.js进行的预览,我发现这个可以兼容安卓和苹果手机,而且预览速度很快。
pdf使用的是pdf.min.js进行的预览。--就这玩意不好整
代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>文件预览工具</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: #f5f5f5;
color: #333;
height: 100vh;
}
.modal-content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
#pdf-container {
width: 100%;
flex-grow: 1;
}
#video-container {
width: 100%;
margin: auto;
flex-grow: 1;
display: flex;
align-items: center;
}
#image-container {
width: 100%;
min-height: 100vh;
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
}
#image-container img {
width: 100%;
}
.loading {
width: 100%;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
color: white;
}
/* 如果你想要一个旋转动画 */
.loading::after {
content: "";
width: 30px;
height: 30px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.error {
color: white;
text-align: center;
margin-top: 50%;
padding: 20px;
}
.video-js .vjs-big-play-button{
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%);
}
/* PDF 控制栏样式 */
.pdf-controls {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
padding: 10px 15px;
border-radius: 20px;
display: flex;
align-items: center;
color: white;
z-index: 100;
}
.pdf-controls button {
background: none;
border: none;
color: white;
font-size: 16px;
padding: 5px 10px;
cursor: pointer;
}
.pdf-controls span {
margin: 0 10px;
}
.pdf-page {
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
width: 100% !important;
height: auto !important;
}
.pdf-page canvas {
width: 100% !important;
height: auto !important;
display: block;
}
</style>
<!-- Video.js 视频播放器 -->
<link href="https://vjs.zencdn.net/7.20.3/video-js.css" rel="stylesheet">
<script src="https://vjs.zencdn.net/7.20.3/video.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script>
<script>
// 设置PDF.js worker路径
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.worker.min.js';
</script>
</head>
<body>
<!-- 预览模态框 -->
<div id="preview-modal" class="modal">
<div class="modal-content">
<div id="image-container" style="display: none;">
<img id="preview-image" src="" class="img-box">
</div>
<div id="video-container" style="display: none;">
<video id="preview-video" class="video-js" controls playsinline style="width: 100%;height: 100vh;"></video>
</div>
<div id="pdf-container" style="display: none;">
</div>
<div id="loading" class="loading">
</div>
<div id="error" class="error" style="display: none;"></div>
<!-- PDF 控制栏 -->
<div id="pdf-controls" class="pdf-controls" style="display: none;">
<button id="prev-page">上一页</button>
<span id="page-num">1 / 1</span>
<button id="next-page">下一页</button>
</div>
</div>
</div>
<script>
// 获取URL参数
function getQueryParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
// 获取文件类型
function getFileType(url) {
if (!url) return null;
// 提取文件扩展名
const extension = url.split('.').pop().toLowerCase().split('?')[0];
// 图片类型
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
if (imageTypes.includes(extension)) return 'image';
// 视频类型
const videoTypes = ['mp4', 'webm', 'ogg', 'mov'];
if (videoTypes.includes(extension)) return 'video';
// PDF类型
if (extension === 'pdf') return 'pdf';
return null;
}
// 预览文件函数
function previewFile(url) {
const modal = document.getElementById('preview-modal');
const loading = document.getElementById('loading');
const errorDiv = document.getElementById('error');
const imageContainer = document.getElementById('image-container');
const videoContainer = document.getElementById('video-container');
const pdfContainer = document.getElementById('pdf-container');
// 显示加载中和模态框
loading.style.display = 'flex';
errorDiv.style.display = 'none';
imageContainer.style.display = 'none';
videoContainer.style.display = 'none';
pdfContainer.style.display = 'none';
modal.style.display = 'block';
console.log('加载开始');
// 获取文件类型
const fileType = getFileType(url);
if (!fileType) {
loading.style.display = 'none';
errorDiv.style.display = 'block';
errorDiv.textContent = '不支持的文件类型或URL格式不正确';
return;
}
// 根据文件类型处理
if (fileType === 'image') {
const img = document.getElementById('preview-image');
img.alt = '图片预览';
img.onload = function() {
loading.style.display = 'none';
imageContainer.style.display = 'flex';
};
img.onerror = function() {
showError('图片加载失败');
};
img.src = url;
}
else if (fileType === 'video') {
loading.style.display = 'none';
videoContainer.style.display = 'flex';
// 初始化视频播放器
const player = videojs('preview-video', {
controls: true,
autoplay: true,
preload: 'auto',
playsinline: true,
poster: url + '?vframe/png/offset/1',
sources: [{
src: url,
type: getVideoMimeType(url)
}]
});
player.on('error', function() {
showError('视频加载失败');
});
// 存储播放器实例以便关闭时销毁
modal.dataset.player = player;
}
else if (fileType === 'pdf') {
pdfContainer.style.display = 'block';
const previewContainer = document.getElementById('pdf-container');
// 存储PDF相关变量
let pdfDoc = null;
let currentPage = 1;
let pageRendering = false;
let pageNumPending = null;
const scale = 1.0;
// 获取DOM元素
const prevPageBtn = document.getElementById('prev-page');
const nextPageBtn = document.getElementById('next-page');
const pageNumSpan = document.getElementById('page-num');
// 渲染PDF页面
function renderPage(num) {
pageRendering = true;
// 使用promise获取页面
pdfDoc.getPage(num).then(function(page) {
const viewport = page.getViewport({ scale: scale });
// 创建容器div
const pageDiv = document.createElement('div');
pageDiv.className = 'pdf-page';
pageDiv.id = `page-${num}`;
// 移除旧的页面元素
const oldPage = document.getElementById(`page-${num}`);
if (oldPage) oldPage.remove();
// 创建canvas元素
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// 设置canvas尺寸
canvas.height = viewport.height;
canvas.width = viewport.width;
// 将canvas添加到页面容器
pageDiv.appendChild(canvas);
// 将页面容器添加到PDF容器
pdfContainer.appendChild(pageDiv);
// 渲染PDF页面到canvas上
const renderContext = {
canvasContext: context,
viewport: viewport
};
const renderTask = page.render(renderContext);
// 渲染完成
renderTask.promise.then(function() {
pageRendering = false;
if (pageNumPending !== null) {
// 有新页面要渲染
renderPage(pageNumPending);
pageNumPending = null;
}
// 更新页面显示
pageNumSpan.textContent = `${currentPage} / ${pdfDoc.numPages}`;
// 加载完成
if (currentPage === 1) {
loading.style.display = 'none';
}
});
});
}
// 跳转到指定页面
function gotoPage(num) {
if (pageRendering) {
pageNumPending = num;
} else if (num !== currentPage && num > 0 && num <= pdfDoc.numPages) {
currentPage = num;
renderPage(currentPage);
// 滚动到页面顶部
const pageElement = document.getElementById(`page-${currentPage}`);
if (pageElement) {
pageElement.scrollIntoView();
}
}
}
// 上一页
prevPageBtn.onclick = function() {
if (currentPage <= 1) return;
gotoPage(currentPage - 1);
};
// 下一页
nextPageBtn.onclick = function() {
if (currentPage >= pdfDoc.numPages) return;
gotoPage(currentPage + 1);
};
// 异步加载PDF文档
pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
pdfDoc = pdfDoc_;
// 更新页面总数显示
pageNumSpan.textContent = `1 / ${pdfDoc.numPages}`;
// 初始渲染第一页
renderPage(1);
// 预加载后续页面
for (let i = 2; i <= Math.min(999999, pdfDoc.numPages); i++) {
renderPage(i);
}
}).catch(function(error) {
console.error('Error loading PDF:', error);
showError('PDF加载失败');
});
}
}
// 获取视频MIME类型
function getVideoMimeType(url) {
const extension = url.split('.').pop().toLowerCase().split('?')[0];
switch(extension) {
case 'mp4': return 'video/mp4';
case 'webm': return 'video/webm';
case 'ogg': return 'video/ogg';
default: return 'video/mp4';
}
}
// 显示错误信息
function showError(message) {
document.getElementById('loading').style.display = 'none';
const errorDiv = document.getElementById('error');
errorDiv.style.display = 'block';
errorDiv.textContent = message;
}
// 关闭模态框
function closeModal() {
const modal = document.getElementById('preview-modal');
modal.style.display = 'none';
// 如果有视频播放器实例,则销毁它
if (modal.dataset.player) {
modal.dataset.player.dispose();
delete modal.dataset.player;
}
// 如果有图片查看器实例,则销毁它
if (modal.dataset.viewer) {
modal.dataset.viewer.destroy();
delete modal.dataset.viewer;
}
}
// 点击模态框背景关闭
document.getElementById('preview-modal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// 页面加载完成后自动处理
document.addEventListener('DOMContentLoaded', function() {
const fileUrl = getQueryParam('url');
if (fileUrl) {
previewFile(fileUrl);
} else {
showError('未提供文件URL参数');
document.getElementById('preview-modal').style.display = 'block';
document.getElementById('loading').style.display = 'none';
}
});
</script>
</body>
</html>
使用:
像这样传递参数就行。