前端导出 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 给出的代码,查下具体文档、案例修修改改就知道怎么做了,没什么复杂,反正记录一下,水水文章吧🤣

相关推荐
张拭心13 分钟前
拭心 7 月日复盘|个体在 AI 时代的挑战
前端
这是个栗子23 分钟前
express-jwt报错:Error: algorithms should be set
前端·npm·node.js
Dolphin_海豚26 分钟前
vapor 的 IR 是如何被 generate 到 render 函数的
前端·vue.js·vapor
小妖66630 分钟前
Next.js 怎么使用 Chakra UI
前端·javascript·ui
胡西风_foxww36 分钟前
从数据丢失到动画流畅:React状态同步与远程数据加载全解析
前端·javascript·react.js·同步·异步·数据·状态
格调UI成品37 分钟前
[特殊字符] 数据可视化结合 three.js:让 3D 呈现更精准,3 个优化经验谈
javascript·3d·信息可视化
初遇你时动了情1 小时前
JS中defineProperty/Proxy 数据劫持 vue3/vue2双向绑定实现原理,react 实现原理
javascript·vue.js·react.js
阿华的代码王国2 小时前
【Android】RecyclerView实现新闻列表布局(1)适配器使用相关问题
android·xml·java·前端·后端
汪子熙2 小时前
Angular 最新的 Signals 特性详解
前端·javascript
Spider_Man2 小时前
前端路由双雄传:Hash vs. History
前端·javascript·html