前端导出 pdf 与 跑马灯效果 最佳实践

前言

最近做了两个功能,一个是前端导出 pdf ,一个是跑马灯效果,两者都有不同的实现方案,本文记录一下实现这两种功能最优的方案

前端导出pdf

实现前端导出 pdf 主要有以下两种方案

html2canvas + jspdf

实际是将 html 转成图片,再用 jspdf 导出

优点:

  1. 样式可以更精美:html长啥样,导出后就是啥样

缺点

  1. 文件大:因为导出的是图片
  2. 分页时内容会被截断:分页相当于把图片切割成多页,这会导致有些文字被截断

综合来看,这种方案只适合导出 短html 的情况,如果是 长html,缺点就会体现得淋漓尽致!

由于我最终采用的是第二种方案,所以这种不多说了

html-to-pdfmake + pdfmake

使用 htmlToPdfMake 将 html 节点转成 pdfmake 所需的格式后进行导出

优点

  1. 文件小:因为导出的是文本
  2. 展示完整:插件会自动处理好分页,不会出现文字截断的情况,显示完整

缺点

  1. 样式相对简陋
  2. 原生不支持中文,需要配置字体,比较繁琐

由于我需要导出 长html 所以选择了这种方案

使用

原生不支持中文,需要自己配置字体,这点比较繁琐

第一步:配置字体

查看官方文档,可知:

  1. 找一个中文字体,(直接在系统字体找个中文字体,如"宋体",比较方便但可能要注意下版权!)

  2. 将中文字体文件,复制到 ./node_modules/pdfmake/examples/fonts

  3. 执行插件自带的脚本 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 给出的代码,查下具体文档、案例修修改改就知道怎么做了,没什么复杂,反正记录一下,水水文章吧🤣

相关推荐
yvvvy9 分钟前
前端性能优化全家桶:从重绘重排到面试连招,一篇搞懂
前端·javascript·面试
1024小神12 分钟前
微信小程序原生wxml中的事件函数
前端
小蒜学长22 分钟前
vue家教预约平台设计与实现(代码+数据库+LW)
java·数据库·vue.js·spring boot·后端
BigTopOne25 分钟前
[kotlin] inline 函数
前端
怪可爱的地球人25 分钟前
typescript-接口
前端
串串狗xk26 分钟前
使用 webgl 写的新概念笔记应用《赛博城寨》,在三维开放世界里写笔记
javascript·webgl
我是ed28 分钟前
# vue实现拖拉拽效果,类似于禅道首页可拖拽排布展示内容(插件-Grid Layout)
前端
东风西巷32 分钟前
微软恶意软件删除工具:官方免费的系统安全防护利器
前端·安全·microsoft·系统安全·软件需求
跟橙姐学代码35 分钟前
手把手教你玩转 multiprocessing,让程序跑得飞起
前端·python·ipython
华仔啊40 分钟前
SpringBoot+MySQL+Vue实现文件共享系统
java·前端·后端