最后
给大家分享一些关于HTML的面试题。
笔锋签名
我是用开源项目 smooth-signature 实现带笔锋签名的功能。
Gitee 地址是 github.com/linjc/smoot…
npm install --save smooth-signature
使用起来也比较简单,首先获取到需要操作的画布 canvas ,然后生成一个笔锋签名对象 SmoothSignature,optionSign 是初始化的一些简单属性。
const signature = new SmoothSignature(canvas, optionSign);
这样一来,我们的 canvas 就可以画线条了,同时我们可以通过 signature 去做一些操作,比如清空签名、撤回一步的操作等。
方案一
实现要点
- 读取 PDF 文件,并将 PDF 页面渲染到 Canvas 画布上,这里需要动态生成 Canvas
- 将每一个 Canvas 都包装成 SmoothSignature
- 添加一个标识,判断是否允许在 Canvas 上画线(手机滑动会和签名画线冲突,用按钮来控制什么时候允许画线)。
- 保存 PDF 时,先将每一个 Canvas 中的内容转化成图片格式 image/JPEG ,或者 image/PNG ,PNG格式的文件可能会比较大。
- 最后用生成的图片导出一个新的 PDF (实质上 PDF 每一页都是一张图片)。
实现过程
组件引用
| smooth-signature | 笔锋签名 |
| pdfjs-dist | PDF展示等功能 |
| jspdf | PDF导出相关功能 |
npm install --save smooth-signature
npm install --save pdfjs-dist@2.0.943
npm install --save jspdf
页面元素
主要是读取文件、切至签名功能、切回预览功能、撤回签名、清除所有签名以及下载PDF的功能。
<template>
<div :class="`tab-header`">
<div id="editor">
<Input
:class="`button-common`"
type="file"
ref="fielinput"
accept=".pdf"
id="fielinput"
@change="uploadFile"
/>
<Button :class="`button-common`" v-if="isSign" @click="handleSign">切回预览</Button>
<Button :class="`button-common`" v-else @click="handleSign">切至签名</Button>
<Button :class="`button-common`" @click="handleUndo">撤回</Button>
<Button :class="`button-common`" @click="handleClear">清除</Button>
<Button :class="`button-common`" @click="savePDF">下载PDF</Button>
</div>
<div>
<div id="parentDiv">
<div ref="contentDiv" id="contentDiv"></div>
</div>
</div>
</div>
</template>
<script lang="ts">
引用
......
实现代码
......
</script>
<style lang="less" scoped>
.tab-header {
background: rgb(146, 175, 138);
padding-left: 1%;
padding-right: 1%;
}
.button-common {
margin-right: 2px;
max-width: 200px;
}
#contentDiv {
// display: inline-block;
}
#parentDiv {
position: absolute;
overflow: auto;
top: 5%;
bottom: 1%;
display: inline-block;
}
#signShower {
position: absolute;
left: 50%;
top: 5%;
bottom: 1%;
display: inline-block;
}
</style>
添加引用
这里要注意的是,需要给 pdfJS 指定工作路径
import { Button, Input } from 'ant-design-vue';
import { defineComponent, ref } from 'vue';
import SmoothSignature from 'smooth-signature';
import \* as pdfJS from 'pdfjs-dist';
import \* as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
import JsPDF from 'jspdf';
pdfJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;
实现代码
代码中添加了主要的注释,可以查看下述代码
export default defineComponent({
components: { Button, Input },
setup() {
const fielinput = ref(null);
const contentDiv = ref(null);
//签名相关
const isSign = ref(false); //控制是否允许签名
const canvass = ref([]); //保存所有画布元素
const signatures = ref([]); //所有签名对象
const historys = ref([]); //签名历史 用于回退或者清除,因为是一次性展示多个页面,会存在多个包装好的签名对象,存放历史列表方便操作
//PDF展示相关
const pdfData = ref(null); // PDF 内容
const scale = ref(2); //放大比例 ,有的时候展示可能会比较模糊,可以放大展示
//上传控件选择事件,加载选中的 PDF 文件
const uploadFile = (e: Event) => {
// 断言为HTMLInputElement
const target = e.target as HTMLInputElement;
const files = target.files;
let reader = new FileReader();
reader.readAsDataURL(files[0]);
reader.onload = () => {
let data = atob(reader.result.substring(reader.result.indexOf(',') + 1));
loadPdfData(data);
};
};
//加载PDF
function loadPdfData(data) {
//移除所有旧的 Canvas 画布元素
removeChild();
//重置对象状态
isSign.value = false;
canvass.value = [];
signatures.value = [];
// 引入pdf.js的字体,如果没有引用的话字体可能会不显示
let CMAP\_URL = 'https://unpkg.com/pdfjs-dist@2.0.943/cmaps/';
//读取base64的pdf流文件
pdfData.value = pdfJS.getDocument({
data: data, // PDF base64编码
cMapUrl: CMAP\_URL,
cMapPacked: true,
});
//渲染全部页面
renderAllPages();
}
//移除页面上旧的元素
function removeChild() {
var content = contentDiv.value;
var child = content.lastElementChild;
while (child) {
content.removeChild(child);
child = content.lastElementChild;
}
}
//渲染全部页面
function renderAllPages() {
pdfData.value.promise.then((pdf) => {
for (let i = 1; i <= pdf.numPages; i++) {
pdf.getPage(i).then((page) => {
let viewport = page.getViewport(scale.value);
//动态生成 Canvas 画布并设置宽高
var canvas = document.createElement('canvas');
canvas.height = viewport.height;
canvas.width = viewport.width;
let ctx = canvas.getContext('2d');
let renderContext = {
canvasContext: ctx,
viewport: viewport,
};
//将 PDF 页面渲染到 Canvas 上
page.render(renderContext).then(() => {});
//将画布包装成 SmoothSignature
initSignatureCanvas(canvas);
//将画布元素放入到 div 容器中展示
canvass.value.push(canvas);
contentDiv.value.appendChild(canvas);
});
}
});
}
//初始化签名对象
const initSignatureCanvas = (canvas) => {
const optionSign = {
width: canvas.width,
height: canvas.height,
maxHistoryLength: 100, //最大历史记录
};
const signature = new SmoothSignature(canvas, optionSign);
//初始化时 先移除它内部添加的监听事件,默认不能签名
signature.removeListener();
//签名对象 addHistory 方法做一下修改,在原来逻辑的基础上添加这一行
// historys.value.push(signature); 方便处理历史签名记录
signature.addHistory = function () {
if (!signature.maxHistoryLength || !signature.canAddHistory) return;
signature.canAddHistory = false;
signature.historyList.push(signature.canvas.toDataURL());
signature.historyList = signature.historyList.slice(-signature.maxHistoryLength);
historys.value.push(signature);
};
signatures.value.push(signature);
};
/\*\*
\* 签名预览转换
\* 允许签名:调用 signature 对象中的 addListener 方法,添加监听事件
\* 不允许签名:调用 signature 对象中的 removeListener 方法,移除监听事件
\*/
const handleSign = () => {
isSign.value = !isSign.value;
if (signatures.value && signatures.value.length > 0) {
if (isSign.value) {
for (let i = 0; i < signatures.value.length; i++) {
signatures.value[i].addListener();
}
} else {
for (let i = 0; i < signatures.value.length; i++) {
signatures.value[i].removeListener();
}
}
}
};
/\*\*
\* 后退操作
\* 调用历史签名记录中的 signature 对象中的 undo 方法会撤回当前对象中的最后一次的画线记录
\* 注意:后退后不要忘记将列表中最后一个元素移除
\*/
const handleUndo = () => {
if (historys.value && historys.value.length > 0) {
const signatureList = historys.value;
let signature = signatureList.pop();
signature.undo();
historys.value = signatureList;
}
};
// 清除所有 循环把所有签名历史都处理了
const handleClear = async () => {
while (historys.value && historys.value.length > 0) {
handleUndo();
}
};
// 下载PDF
const savePDF = () => {
//生成新的 PDF
let pdf = new JsPDF('', 'pt', 'a4');
if (canvass.value.length > 0) {
//将 canvas 内容转化成 JPEG
for (let i = 0; i < canvass.value.length; i++) {
const ccccc = canvass.value[i];
let pageData = ccccc.toDataURL('image/JPEG');
if (i > 0) {
pdf.addPage();
}
pdf.addImage(
pageData,
'JPEG',
0,
0,
ccccc.width / scale.value,
ccccc.height / scale.value,
);
}
//到处新的PDF
return pdf.save('TestPdf.pdf');
}
};
return {
fielinput,
uploadFile,
contentDiv,
isSign,
handleSign,
handleUndo,
handleClear,
savePDF,
};
},
mounted() {},
});
效果展示
缺点
1、生成的新的PDF每一页都是一个图片,这就表示 PDF 中的内容无法被解析,文字再也无法被选中了。
2、因为生成的是图片,所以最终效果可能会变模糊,可以通过放大比例去优化展示效果,但是始终不是一个最优的解决方案。
方案二
方案二使用一个新的组件 pdf-lib 来处理最后生成的 PDF
方案二仍旧使用 pdfjs-dist 在 Canvas 上展示 PDF,并使用 smooth-signature 使得画布拥有笔锋签名效果。
不同的是,这一次签名画布和 PDF 展示画布并不再是同一个画布,而是上下重叠的两个分离的画布
这样一来,我们可以将签名画布上的内容生成一个透明背景的 PNG 图片,然后以水印的方式添加到原来的 PDF 文件中。
修改页面元素
需要两个 Div 容器 ,父容器的滚动条需要同步滚动,否则会出现签名在滚动,但是 PDF 页面不动的情况
<template>
<div :class="`tab-header`">
<div id="editor">
<Input
:class="`button-common`"
type="file"
ref="fielinput"
accept=".pdf"
id="fielinput"
@change="uploadFile"
/>
<Button :class="`button-common`" v-if="isSign" @click="handleSign">点击预览</Button>
<Button :class="`button-common`" v-else @click="handleSign">点击签名</Button>
<Button :class="`button-common`" @click="handleUndo">撤回</Button>
<Button :class="`button-common`" @click="handleClear">清除</Button>
<Button :class="`button-common`" @click="savePDF">下载PDF</Button>
</div>
<div>
<div id="parentDiv1">
<div ref="contentDiv" id="contentDiv"></div>
</div>
<div id="parentDiv2">
<div ref="signContentDiv" id="signContentDiv"></div>
</div>
</div>
</div>
</template>
替换引用
//import JsPDF from 'jspdf';
import { PDFDocument } from 'pdf-lib';
修改代码
文章底部附完整代码
...
const signCanvass = ref([]); //保存所有签名画布
const base64 = ref(null); //读取的pdf的base64数据
上传文件的方法中添加一行保存PDF base64 ,生成新的 PDF 时使用
const uploadFile = (e: Event) => {
...
reader.onload = () => {
base64.value = reader.result;
...
};
};
加载 PDF 时,我们要重置的对象增加了,而且加载完之后我们要让两个父容器滚动同步
框架相关
原生JS虽能实现绝大部分功能,但要么就是过于繁琐,要么就是存在缺陷,故绝大多数开发者都会首选框架开发方案。现阶段较热门是React、Vue两大框架,两者工作原理上存在共通点,也存在一些不同点,对于校招来说,不需要两个框架都学得特别熟,一般面试官会针对你简历中写的框架进行提问。
在框架方面,生命周期、钩子函数、虚拟DOM这些基本知识是必须要掌握的,在学习的过程可以结合框架的官方文档
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
Vue框架
知识要点: 1. vue-cli工程 2. vue核心知识点 3. vue-router 4. vuex 5. http请求 6. UI样式 7. 常用功能 8. MVVM设计模式
React框架
知识要点: 1. 基本知识 2. React 组件 3. React Redux 4. React 路由