前言
最近做了两个功能,一个是前端导出 pdf ,一个是跑马灯效果,两者都有不同的实现方案,本文记录一下实现这两种功能最优的方案
前端导出pdf
实现前端导出 pdf 主要有以下两种方案
html2canvas + jspdf
实际是将 html 转成图片,再用 jspdf 导出
优点:
- 样式可以更精美:html长啥样,导出后就是啥样
缺点
- 文件大:因为导出的是图片
- 分页时内容会被截断:分页相当于把图片切割成多页,这会导致有些文字被截断
综合来看,这种方案只适合导出 短html 的情况,如果是 长html,缺点就会体现得淋漓尽致!
由于我最终采用的是第二种方案,所以这种不多说了
html-to-pdfmake + pdfmake
使用 htmlToPdfMake 将 html 节点转成 pdfmake 所需的格式后进行导出
优点
- 文件小:因为导出的是文本
- 展示完整:插件会自动处理好分页,不会出现文字截断的情况,显示完整
缺点
- 样式相对简陋
- 原生不支持中文,需要配置字体,比较繁琐
由于我需要导出 长html 所以选择了这种方案
使用
原生不支持中文,需要自己配置字体,这点比较繁琐
第一步:配置字体

查看官方文档,可知:
-
找一个中文字体,(直接在系统字体找个中文字体,如"宋体",比较方便但可能要注意下版权!)
-
将中文字体文件,复制到
./node_modules/pdfmake/examples/fonts
-
执行插件自带的脚本
node build-vfs.js "examples/fonts"
执行后会在 build 文件夹生成 /vfs_fonts.js ,官方推荐将这个文件复制出来使用,而不是直接引用
第二步:配置这个生成的字体文件
可以使用 addFonts
方法,或者直接配置到 fonts
属性,例子中 yourFontName 就是字体的命名,fontFile.ttf 对应生成的 vfs_fonts.js
中的 key
less
// 使用 addFonts 方法
pdfMake.addFonts({
yourFontName: {
normal: 'fontFile.ttf',
bold: 'fontFile2.ttf',
italics: 'fontFile3.ttf',
bolditalics: 'fontFile4.ttf'
}
});
// 或者直接配置到fonts属性
pdfMake.fonts = {
yourFontName: {
normal: 'fontFile.ttf',
bold: 'fontFile2.ttf',
italics: 'fontFile3.ttf',
bolditalics: 'fontFile4.ttf'
},
anotherFontName: {
(...)
}
}
配置好字体即可对 html 节点进行导出,以下是在 vue 中使用的完整例子
xml
<template>
<div>
<div class="table-wrapper" ref="tableWrapper">
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="date"
label="date"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="name"
width="180">
</el-table-column>
<el-table-column
prop="address"
label="address">
</el-table-column>
</el-table>
</div>
<el-button @click="exportPdf">导出pdf</el-button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import pdfMake from 'pdfmake'
import htmlToPdfMake from 'html-to-pdfmake'
import fonts from './vfs_fonts.js' // 插件脚本生成的字体文件
pdfMake.vfs = fonts
pdfMake.fonts = {
chineseFont: {
normal: 'STSONG.TTF',
bold: 'STSONG.TTF',
italic: 'STSONG.TTF',
bolditalic: 'STSONG.TTF',
},
}
const tableData = [{
date: '2016-05-02',
name: '小王',
address: 'shanghai 1518'
}, {
date: '2016-05-04',
name: '小王',
address: 'shanghai 1517'
}, {
date: '2016-05-01',
name: '小王',
address: 'shanghai 1519'
}, {
date: '2016-05-03',
name: '小王',
address: 'shanghai 1516'
}]
const tableWrapper = ref<HTMLElement | null>(null)
const exportPdf = () => {
console.log(tableWrapper.value) // DOM 元素
const content = htmlToPdfMake(tableWrapper.value.innerHTML)
const definition = {
content,
defaultStyle: {
font: 'chineseFont',
},
}
pdfMake.createPdf(definition).download('table.pdf')
}
踩坑
在使用 pdfmake 导出 element-ui 的表格时,会出现表头和表内容对不上的情况

原因是 element 的表头和表格内容实际是两个表格,没有关联性,解决方式是对 htmlToPdfMake 生成的表格数据做一下处理,加一下宽度,关键方法及代码如下
scss
function findAllTables(obj) {
const tables = []
function recurse(node) {
if (typeof node !== 'object' || node === null) return
if (node.table && node.table.body) tables.push(node)
if (node.stack && Array.isArray(node.stack)) {
node.stack.forEach(child => recurse(child))
}
if (Array.isArray(node)) {
node.forEach(item => recurse(item))
}
}
recurse(obj)
return tables
}
const exportPdf = () => {
const content = htmlToPdfMake(tableWrapper.value.innerHTML)
const tables = findAllTables(content)
tables.forEach(tableNode => {
if (!tableNode.table.widths) {
tableNode.table.widths = [100, 100, '*']
}
})
const definition = {
content,
defaultStyle: {
font: 'chineseFont',
},
}
pdfMake.createPdf(definition).download('table.pdf')
}
处理过后,宽度就对上了
跑马灯效果
动画效果无非就是 js 或者 css
js
js 的话就是逐帧改修改属性吧,这种性能差,有卡顿感觉,基本不会使用了,就不细说了
css
css 的话就是使用 css3 的动画属性吧,倒是丝滑,但是滚到最后一个后再回到第一个时会有突兀的感觉

实现如下,可以封装成组件
xml
<template>
<div class="scroll-parent-box">
<div class="scroll-list" :style="{
'animation-duration': `${data.length}s`
}">
<div class="scroll-item" v-for="(item, index) in data" :key="index">
{{ item }}
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
props: {
data: {
type: Array,
default: () => ([])
}
}
}
</script>
<style lang="less" scoped>
@keyframes scrollAnimation {
from {
transform: translateY(0);
}
to {
transform: translateY(calc(-100% + 50px));
}
}
.scroll-parent-box {
height: 56px;
overflow: hidden;
.scroll-list {
transition: all 0ms ease-in 0s;
animation-name: scrollAnimation;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-delay: 0;
.scroll-item {
display: inline-block;
padding: 4px;
border-radius: 4px;
background: rgba(111, 0, 43, 0.40);
backdrop-filter: blur(4.320000171661377px);
color: #FFFCEB;
font-size: 12px;
margin-bottom: 8px;
}
}
}
</style>
swiper
想要实现最后一行到第一行平滑过渡,可以使用 swiper ,其实就是配置成无限滚动,然后把滚动动画改成匀速就行,完整示例如下
xml
<template>
<swiper
:modules="[Autoplay]"
:slides-per-view="1"
:slides-per-group="1"
:loop="true"
:free-mode="true"
direction="vertical"
:speed="1000"
:autoplay="{delay: 0}"
class="my-swiper"
>
<swiper-slide v-for="item in marqueeData" :key="item">
{{ item }}
</swiper-slide>
</swiper>
</template>
<script setup lang="ts">
import { Swiper, SwiperSlide } from 'swiper/vue'
import { Navigation, Pagination, Autoplay } from 'swiper/modules'
import 'swiper/css';
const marqueeData = [
'第一行第一行第一行',
'第二行第二行第二行',
'第三行第三行第三行',
'第四行第四行第四行',
'第五行第五行第五行',
'第六行第六行第六行',
]
</script>
<style>
// 关键是把动画改成匀速
.my-swiper>.swiper-wrapper {
-webkit-transition-timing-function: linear;
-moz-transition-timing-function: linear;
-ms-transition-timing-function: linear;
-o-transition-timing-function: linear;
transition-timing-function: linear;
}
</style>
效果如下

其实这种功能实现 AI 搜一下,基于 AI 给出的代码,查下具体文档、案例修修改改就知道怎么做了,没什么复杂,反正记录一下,水水文章吧🤣