前端能显示文件流的图片吗(纯前端文档预览,还要支持所有主流格式,有这一篇就足够了)
写在前面
纯前端的文档预览功能 ,是非常常见的需求 ,但就是这么简单的需求,难住了许多可爱的小伙伴们 。别急 ,先访问一下解决方案 ,给你一个惊喜 ,再往下看:
文件在线预览DEMO
服务器文件预览DEMO
演示视频
开源项目
项目最新进展(2023年4月10日):
1. Vue3版本重构了Xlsx模块 ,使用Worker加载 ,增加了体验性 ,能够秒级打开千万行的Excel
2. 增加了Markdown文档的渲染 ,能够美观的渲染md文档 ,使用了github风格
4. 新版文档增加视频演示 ,更直观展示功能 。
项目最新进展(2023年4月4日):
1. Vue3+TypeScript+Vite版本发布,完全重构 ,性能飞跃 ,超高代码质量
2. 重构了Xlsx对于主题颜色的获取和计算,能够完美显示颜色
3. 重构了Pptx底层部分逻辑 ,解耦了图表部分 ,并优化了显示性能
4. 新版文档已经上线,demo部分替换部署了Vue3版本
2023年2月28日全面升级说明
鉴于很多朋友呼吁文档的问题 ,目前使用文档已更新 ,请参考
使用文档
本次更新属于突破更新 ,完成了项目组件化改造 ,嵌入项目中使用更加容易 ,具体请拉取最新代码体验一下吧!
==更新日志==
1. 优化了pptx嵌套块溢出效果 ,比之前好很多
2. 增加了文件标题显示
3. 进行了组件化拆分设计 ,提供标准的Vue组件 ,方便接入
4. 优化了底层的一些代码 ,运行更加稳定
== 公众号上线!==
此外,博主的公众号上线了 ,大家在微信搜索 "飞鱼开源"。关注“飞鱼开源WorkShop ”公众号 ,可以获取最新源码,同时不定期更新技术福利!
有大家的支持我才有更新的动力 ,感谢大家一直以来的支持!❤❤❤
仓库地址: https://git.flyfish.group ,请下载过资源的大家注册后获取最新源码!
2022年8月1日更新,重大升级 。
1. 重构大部分pptx逻辑 ,优化背景样式 ,块文字样式和图表 。
2. 优化PDF展现逻辑 ,基于官网demo使用官方pdfViewer组件实现懒加载 ,虚拟滚动 ,大幅度提高性能 ,可以秒开超大PDF文件!
3. 优化框架和升级依赖版本
福利:注册git私库并发送账号给博主 ,博主会帮忙开放本项目的git仓库权限 ,永久更新!记得是下载过资源的小伙伴哦 ,开发不易,请予以点滴支持 ,不尽感激!
git仓库地址:飞鱼开源工作室
2022年5月31日更新 。增加文件url输入预览 ,可以访问文件在线预览DEMO体验 。由于很多小伙伴提的问题都是关于服务器URL预览文件怎么预览,这次的demo集成了这部分功能 ,大家可以参照源码进行理解和修改 。
因demo使用ajax加载 ,在测试时请保证文件资源响应Header包含允许跨域的头部 。建议头部如下:
Access-Control-Allow-Origin * Access-Control-Allow-Headers X-Requested-With Access-Control-Allow-Methods GET功能入口如下:
实现效果
word文档预览
Excel文档预览
PPT文档预览
PDF文档预览
Markdown预览
图片预览
文本预览
视频预览
看完了之后,废话不多说 ,来给大家梳理梳理实现思路 。
现存的方案和不足
笔者在接到这个功能需求后 ,对市面上目前的实现方案进行了归纳和梳理 ,不外乎就三种:
PDF预览使用pdfjs ,Office文档使用微软的提供的预览URL 。该方案确实省事 ,而且效果是最好的 ,但是有个很大的问题 ,文件链接必须是公网链接 ,这对于在企业网或局域网部署的系统来说 ,基本上是不可行的方案,pass 使用Java后端统一转换为PDF ,然后在前端预览 。该方案兼容性较好 ,效果仅次于在线Office,但是对于服务器的压力比较大 ,在动辄要“高并发 ,高可用,高吞吐量 ”的互联网场景下总是不那么合适 ,基于OpenOffice的文件转换非常耗费IO ,pass 客户端本地安装Office ,利用浏览器的Office插件进行预览。这种实现方式对客户端要求较高 ,基本上不考虑 ,毕竟我们是web预览嘛 ,谁知道客户用的是啥浏览器 ,pass意外的收获
到此为止 ,所有的方案都被pass掉了 ,非常绝望 。无果后,我搭上梯子 ,疯狂Google ,终于找到了一个jquery的开源插件,叫做officeToHtml ,出于对开源的尊重 ,这里提供一下人家的访问链接:OfficeJs | Demos
这个开源项目非常好用,引用它的demo就能直接预览主流格式 ,但是它是基于JQuery的 。事实上 ,当时我都已经通过这个方案实现了 ,结果我们领导说不是Vue ,而且用的组件也太老了 ,强行pass掉了。现实总是残酷的 ,看着我头顶所剩不多的秀发 ,深深叹了口气 ,准备自己再次开整 。
有了国外大佬的思路提供 ,我的思路也渐渐清晰:
要解决这个问题,还是得用Vue实现 大佬的项目是jquery写的 ,我用Vue实现 ,也没说不让引用jQuery呀 分析一下大佬使用的开源组件,去GitHub上找最新的或者效果最好的 ,说不定有Vue版本呢 自己封装渲染入口 ,根据扩展名动态匹配渲染器,解析需要的格式 。OK ,思路清晰了 ,我们开始撸代码。
开始实现
一 、找替代框架
大佬的框架已经老得不被待见了 ,大致整理后 ,笔者找到的最贴近且效果最好的框架都在下面的表里了:
文档格式 老的开源组件 替代开源组件 word(docx) mammoth docx-preview(npm)powerpoint(pptx)
pptxjspptxjs改造开发
excel(xlsx) sheetjs 、handsontable exceljs(npm)、handsontable(npm) pdf(pdf) pdfjs pdfjs(npm) 图片 jquery.verySimpleImageViewer v-viewer(npm)升级后的组件完全兼容npm ,唯一不兼容的pptxjs也被我改造了 ,能够完美兼容 。以下是package.json中相关的依赖 。
"@handsontable/vue": "^11.1.0", "docx-preview": "^0.1.8", "exceljs": "^4.3.0", "handsontable": "^11.1.0", "pdfjs-dist": "^2.12.313", "v-viewer": "^1.6.4", "vue": "^2.6.11"二 、搭建简单的视图组件
框架找好了 ,接下来我们开工 。老样子 ,用vue-cli创建一个hello-world项目 ,把脚手架初始化出来 。如果没安装过,先全局安装一下:
npm install -g @vue/cli-service-global创建项目 ,名字就叫file-viewer吧!
cd ~/Projects vue create file-viewer然后我们在 src/components/HelloWorld.vue中 ,给他加一个容器,用于承载文档视图 。再弄一个简单的loading容器 ,ok 。
注意 ,这里的 @/components/util 是一些常用工具类,主要做二进制数据和字节码 、字符串互转的 。当然 ,文档渲染入口也在里面 ,我们后面说 。
<template> <div :class="{hidden}"> <div class="banner"> <div class="container"> <h1><a href="/">Vue在线文档查看器<input class="file-select" type="file" @change="handleChange"/></a></h1> </div> </div> <div class="container"> <div v-show="loading" class="well loading">正在加载中 ,请耐心等待...</div> <div v-show="!loading" class="well" ref="output"></div> </div> </div> </template> <script> import { getExtend, readBuffer, render } from @/components/util; import { parse } from qs; /** * 支持嵌入式显示 ,基于postMessage支持跨域 * 示例代码: * */ export default { name: HelloWorld, props: { msg: String }, data() { return { // 加载状态跟踪 loading: false, // 上个渲染实例 last: null, // 隐藏头部 ,当基于消息机制渲染 ,将隐藏 hidden: false, } }, methods: { async handleChange(e) { this.loading = true; try { const [ file ] = e.target.files; const arrayBuffer = await readBuffer(file); this.loading = false this.last = await this.displayResult(arrayBuffer, file) } catch (e) { console.error(e) } finally { this.loading = false } }, displayResult(buffer, file) { // 取得文件名 const { name } = file; // 取得扩展名 const extend = getExtend(name); // 输出目的地 const { output } = this.$refs; // 生成新的dom const node = document.createElement(div); // 添加孩子 ,防止vue实例替换dom元素 if (this.last) { output.removeChild(this.last.$el); this.last.$destroy(); } const child = output.appendChild(node); // 调用渲染方法进行渲染 return new Promise((resolve, reject) => render(buffer, extend, child) .then(resolve).catch(reject)); } } } </script> <style scoped> .banner { overflow: auto; text-align: center; background-color: #12b6ff; color: #fff; } .hidden .banner { display: none; } .hidden .well { height: calc(100vh - 12px); } .file-select { position: absolute; left: 5%; top: 17px; margin-left: 20px; } .banner a { color: #fff; } .banner h1 { font-size: 20px; line-height: 2; margin: 0.5em 0; } .well { display: block; background-color: #f2f2f2; border: 1px solid #ccc; margin: 5px; width: calc(100% - 12px); height: calc(100vh - 73px); overflow: auto; } .loading { text-align: center; padding-top: 50px; } .messages .warning { color: #cc6600; } </style>三 、实现渲染入口
写好容器后 ,下一步就是重头戏 ,笔者这里使用匹配模式简单实现了一个渲染入口,代码如下:
// 导入渲染器 import renders from ./renders; // 渲染入口函数 ,包含字节数组 、文件类型 、目标容器 export async function render(buffer, type, target) { const handler = renders[type]; if (handler) { return handler(buffer, target); } return renders.error(buffer, target, type); }具体渲染逻辑我们用声明式的方式进行配置 ,统一放置在vendors目录下,像这样:
之后我们写一个策略配置器 ,去统一导入这些模块:
import { defaultOptions, renderAsync } from docx-preview; import renderPptx from @/vendors/pptx; import renderSheet from @/vendors/xlsx; import renderPdf from @/vendors/pdf; import renderImage from @/vendors/image; import renderText from @/vendors/text; import renderMp4 from @/vendors/mp4; // 假装构造一个vue的包装 ,让上层统一处理销毁和替换节点 const VueWrapper = el => ({ $el: el, $destroy() { // 什么也不需要 nothing to do }, }); const handlers = [ // 使用docxjs支持,目前效果最好的渲染器 { accepts: [ docx ], handler: async (buffer, target) => { const docxOptions = Object.assign(defaultOptions, { debug: true, experimental: true, }); await renderAsync(buffer, target, null, docxOptions) return VueWrapper(target); } }, // 使用pptx2html ,已通过默认值更替 { accepts: [ pptx ], handler: async (buffer, target) => { await renderPptx(buffer, target, null); window.dispatchEvent(new Event(resize)); return VueWrapper(target); }, }, // 使用sheetjs + handsontable ,无样式 { accepts: [ xlsx ], handler: async (buffer, target) => { return renderSheet(buffer, target); }, }, // 使用pdfjs ,渲染pdf ,效果最好 { accepts: [ pdf ], handler: async (buffer, target) => { return renderPdf(buffer, target); } }, // 图片过滤器 { accepts: [ gif, jpg, jpeg, bmp, tiff, tif, png, svg ], handler: async (buffer, target) => { return renderImage(buffer, target); } }, // 纯文本预览 { accepts: [ txt, json, js, css, java, py, html, jsx, ts, tsx, xml, md, log ], handler: async (buffer, target) => { return renderText(buffer, target) }, }, // 视频预览 ,仅支持MP4 { accepts: [ mp4 ], handler: async (buffer, target) => { renderMp4(buffer, target) return VueWrapper(target); }, }, // 错误处理 { accepts: [ error ], handler: async (buffer, target, type) => { target.innerHTML = `<div style="text-align: center; margin-top: 80px">不支持.${type}格式的在线预览 ,请下载后预览或转换为支持的格式</div> <div style="text-align: center">支持docx, xlsx, pptx, pdf, 以及纯文本格式和各种图片格式的在线预览</div>`; return VueWrapper(target); } } ] // 匹配 export default handlers.reduce((result, { accepts, handler }) => { accepts.forEach(type => result[type] = handler) return result; }, {});ok ,大功告成!😄
四 、运行调试
还好我们前期做足了工夫 ,一下子就运行起来了 ,但是很快就遇到了问题:
pptxjs的npm版本无法使用,有很多bug ,只能下载源码自己改bug ,作者已经不维护了 exceljs解析sheet的时候有超级多坑,踩到吐🤮 性能问题 ,遇到几十兆的pdf ,打开超级慢,页面会假死(等待一会就好了) 。这个也需要进一步优化好不容易修改好了这些 ,终于跑起来了。大家可以在我的在线demo看到效果 file-viewerhttp://viewer.flyfish.group/
五 、总结一下
实现这个功能总体来说还是非常困难的 ,除了有很多坑 ,找到可用的开源组件也是耗费了我大量的精力 。好在前期做的努力没有白费 ,成功上线了产品 ,也得到了领导的认可罒ω罒 ,嘿嘿 。现在我把我的项目共享出来 ,我已经把源码上传了。本着对技术尊重的态度 ,大家帮忙打赏一两块钱就可以拿到完整的源码 。
此外 ,我也会不断的优化更新,修改bug ,提升性能 ,希望大家持续关注我,有人关注我就一定会一直努力的!谢谢大家!最后附上链接:
Web端文件预览 ,纯前端Vue实现的file-viewer ,不需要后端,支持所有主流格式 ,附带接入文档和嵌入式引用demo-Web开发文档类资源-CSDN下载
最后的最后 ,希望大家写代码都能无bug!如果文章确实帮到了你 ,麻烦给个关注 ,谢谢!
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!