学习整理Fabric.js如何在画布中添加文字、图片和使用按钮和键盘delete键删除功能

本文介绍如何使用Fabric.js在HTML5 Canvas上添加和删除文字及图片。通过示例代码演示了创建可编辑文本、响应键盘事件、图片上传处理等功能,并提供了完整的HTML和JavaScript实现。

原图

在这里插入图片描述

代码

index.html

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">

    <title>Fabric.js 添加文字、删除文字</title>

    <script src="../fabric5.2.1.js"></script>

    <style>
        div#container {
            padding: 30px;
            font-family: 'verdana', lucida;
        }

        input {
            background-color: #ccc;
            padding: 0;
            width: 300px;
            color: #777;
        }

        a {
            color: #777;
            display: block;
            background-color: #ccc;
            width: 300px;
            padding: 0;
            margin-top: 2px;
            text-decoration: none;
        }
    </style>

</head>
<body>

<div id="container">

    <p>
        <input  type="text" id="default_text" name="default_text" value="我是要添加的文字内容">
    </p>
    <p>
        <button onclick="add_text()">添加</button>
    </p>
     <p>
        <input type="file" id="imageLoader" name="imageLoader"/>
    </p>
    <p>
        <button onclick="deleteob()">删除</button>
    </p>

    <canvas id="imageCanvas" width="300" height="300"></canvas>
    <a id="lnkDownload" href="#">点我保存图片</a>
</div>

<script src="script.js"></script>

</body>
</html>

script.js

var canvas = new fabric.Canvas('imageCanvas', {
    backgroundColor: 'rgb(240,240,240)',
    includeDefaultValues: false,// 指示toObject/toDatalessObject是否应该包含默认值,如果设置为false,则优先于对象值
    perPixelTargetFind: true, //这一句说明选中的时候以图形的实际大小来选择而不是以边框来选择
    hasBorders: false,
});

canvas.setWidth(500);
canvas.setHeight(500);


// 使用 IText,可编辑文本
var text_1 = new fabric.IText(
    '《天净沙·秋思》\n枯藤老树昏鸦,\n小桥流水人家,\n古道西风瘦马。\n夕阳西下,\n断肠人在天涯。',
    {
        width: 300,
        fontSize: 14,
        fontFamily: 'Comic Sans',
        left: 50,
        top: 100,
        fill: 'blue',
        splitByGrapheme: true, // 自动换行
    }
);
canvas.add(text_1);


function add_text() {
    var default_text = document.getElementById('default_text');
    let str = default_text.value;
    let text_new = new fabric.Textbox(
        str,
        {
            width: 300,
            fontSize: 14,
            fontFamily: 'Comic Sans',
            left: 10,
            top: 10,
            fill: 'blue',
            splitByGrapheme: true, // 自动换行
        }
    );
    //禁止下拉,防止字体变形
    text_new.setControlVisible('mt', false);
    text_new.setControlVisible('mb', false);
    canvas.add(text_new);
}



//添加图片元素
var imageLoader = document.getElementById('imageLoader');
imageLoader.addEventListener('change', handleImage, false);

function handleImage(e) {

    console.log('handleImage.e==', e);
    var reader = new FileReader();
    reader.onload = function (event) {

        // 转换成base64格式
        var base64Img = reader.result;

        console.log('canvas.width==', canvas.width);
        console.log('canvas.height==', canvas.height);


        //加一张图片
        fabric.Image.fromURL(base64Img, function (img) {
            img.scale(0.2);//缩小0.4倍

            canvas.add(img);
        }, {
            left: 100, // 图片相对画布的左侧距离
            top: 100, // 图片相对画布的顶部距离
            angle: 30, // 图片旋转角度
            opacity: 0.85, // 图片透明度
            // 这里可以通过scaleX和scaleY来设置图片绘制后的大小,这里为原来大小的一半
            scaleX: 0.5,
            scaleY: 0.5
        });
    }
    reader.readAsDataURL(e.target.files[0]);
}

function deleteob() {


    canvas.remove(canvas.getActiveObject());

    canvas.renderAll();
}



//得到按键的信息
function showkey() {
    key = event.keyCode;
    switch (key) {
        case 37://"按了←键!"
            alert("按了←键!");
            break;
        case 38://"按了↑键!"
            alert("按了↑键!");
            break;
        case 39://"按了→键!"
            alert("按了→键!");
            break;
        case 40://"按了↓键!"
            alert("按了↓键!");
            break;
        case 46://"按了delete键!"
            alert("按了delete键!");
            canvas.remove(canvas.getActiveObject());
            break;
    }
}
document.onkeydown = showkey;



var imageSaver = document.getElementById('lnkDownload');
imageSaver.addEventListener('click', saveImage, false);

function saveImage(e) {

    console.log('toJSON==', canvas.toJSON());

    console.log('toObject==', canvas.toObject()); // 输出序列化的内容

    this.href = canvas.toDataURL({
        format: 'png',
        quality: 0.8
    });
    this.download = 'canvas.png';

}

效果图

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

<!-- * @Description: * @autor: liuleilei * @Date: 2025-06-09 13:52:09 * @LastEditors: liuleilei * @LastEditTime: 2025-06-09 15:10:50 --> <template> <div class="drag-sign-seal"> <div id="elesign" class="elesign scrollbar vc-page"> <el-row style="height: 100%"> <div class="page"> <el-button size="small" class="btn-outline-default" @click="prevPage" >上一页</el-button > <el-button size="small" class="btn-outline-default" @click="nextPage" >下一页</el-button > <el-button size="small" class="btn-outline-default" >{{ pageNum }}/{{ numPages }}页</el-button > <el-input-number size="small" style="margin-left: 0.1rem; border-radius: 5px" v-model="currentPage" :min="1" :max="numPages" label="输入页码" @blur="nullity" ></el-input-number> <el-button size="small" class="btn-outline-default" @click="cutover" >跳转</el-button > <el-button size="small" class="btn-outline-default" @click="zoomOut">缩小</el-button> <el-button size="small" class="btn-outline-default">{{ Math.round(currentScale * 100) }}%</el-button> <el-button size="small" class="btn-outline-default" @click="zoomIn">放大</el-button> </div> <div class="col-left-box" id="col-left-box" ref="colLeftBox"> <el-col :span="20" class="pCenter col-left" id="col-left" ref="colLeft"> <div class="canvas-div-box" ref="canvasDivBox"> <div class="canvas-div" id="canvas-div" ref="canvasDiv" v-loading="showLoadingChild"> <!-- pdf部分 --> <canvas style="width: 100%; height: 100%;" id="the-canvas"/> <!-- 盖章部分 --> <canvas style="width: 100%; height: 100%; border: none !important" id="ele-canvas"></canvas> </div> </div> </el-col> </div> <el-col :span="4" class="col-right"> <el-button size="small" type="primary" @click="removeSignature"> 清除签章</el-button > <el-tree :data="menuTree" :props="elTreeProps" default-expand-all :expand-on-click-node="false" > <div slot-scope="{ node, data }"> <div class="custom-tree-node" v-if="!data.pid && !data.isOwnSign"> {{ data.name }} </div> <draggable v-if="data.pid" class="draggable-list scrollbar" v-model="mainImagelist" :group="{ name: 'itext', pull: 'clone' }" :sort="false" @end="end($event, data)" > <transition-group type="transition"> <template> <span :key="data.url"> <img :src="data.url" width="100%;" height="100%" class="imgstyle" /> </span> <div v-if="data.name">{{ data.name }}</div> </template> </transition-group> </draggable> <div v-if="data.sealName" style="text-align: center;white-space: pre-wrap;"> {{ data.sealName }} </div> </div> </el-tree> </el-col> </el-row> </div> <!-- 加载中遮罩 --> <van-overlay :show="showLoadingChild"> <van-loading class="van-loading" style="height: 100%"> </van-loading> </van-overlay> </div> </template> <script> import { getPdfFile, findSignListByUserId, findCanUseSealList, getFileServerConfig, } from "@/api/module/formManggeApi"; import { fabric, cache } from "fabric"; import draggable from "vuedraggable"; import pdf from "vue-pdf"; import CMapReaderFactory from "vue-pdf/src/CMapReaderFactory.js"; const SIGN = "SIGN"; // 签字 const STAMP = "STAMP"; // 印章 export default { name: "drag-sign-seal", props: { entity: { type: Object, default: () => { return {}; }, }, isPortrait: { type: Boolean, default: true }, sealParameter: { type: Object, default: () => { return {}; }, }, fileObj: { type: Object, default: () => { return {}; }, }, sealSignPositionFieldMap: { type: Object, default: () => { return {}; }, }, isOwnSign: { type: Boolean, default: false, }, addr: { type: String, default: '', }, pageN: { type: Number, default: 1, }, isScroll: { type: Boolean, default: false, } }, components: { draggable, }, data() { return { touchStartHandler: null, touchMoveHandler: null, fileUrl: '', menuTree: [ { name: "电子印章", children: [], pid: null, isOwnSign: false }, { name: "本人签名", pid: null, children: [], isOwnSign: false }, ], elTreeProps: { children: "children", label: "name", }, showLoadingChild: false, signs: {}, //pdf预览 pdfUrl: "", pdfDoc: null, numPages: 1, pageNum: 1, scale: 2, currentScale: 1, minScale: 0.5, // 最小缩放比例 maxScale: 5.0, // 最大缩放比例 scaleStep: 0.1, // 缩放步长 pageRendering: false, pageNumPending: null, canvas: null, ctx: null, canvasEle: null, whDatas: null, mainImagelist: [], signList: [], sealList: [], sealMap: {}, activeName: "", currItemIndex: {}, dateList: [], fileType: "", previewFile: null, mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", currentPage: 1, mobileScale: 0.2, objectMovingFlag:false, }; }, computed: { userInfo() { return this.$store.state.user; }, }, watch: { fileObj: { handler() { if (!this.fileObj || !this.fileObj?.fileId) { return; } this.init(); }, deep: true, immediate: true, }, whDatas: { handler() { // this.showLoading = true; if (this.whDatas) { // this.showLoading = false; this.renderFabric(); this.canvasEvents(); } }, immediate: false, deep: true, }, pageNum: { handler(newVal, oldVal) { this.commonSign(this.pageNum); this.queueRenderPage(this.pageNum); this.currentPage = newVal; }, deep: true, }, sealSignPositionFieldMap: { handler(n) { if (n && !n.length) { this.canvasEle.remove(this.canvasEle.clear()) } }, deep: true } }, created() {}, mounted() { if (!this.isScroll) { this.initScroll() } this.enableScroll(); }, methods: { enableScroll() { let _this = this; const container = this.$refs.canvasDivBox; let isDown = false; let startX, startY; let scrollLeft, scrollTop; // 鼠标事件 container.addEventListener('mousedown', (e) => { if(_this.objectMovingFlag){ return; } isDown = true; startX = e.pageX - container.offsetLeft; startY = e.pageY - container.offsetTop; scrollLeft = container.scrollLeft; scrollTop = container.scrollTop; }); container.addEventListener('mouseleave', () => { if(_this.objectMovingFlag){ return; } isDown = false; }); container.addEventListener('mouseup', () => { if(_this.objectMovingFlag){ return; } isDown = false; }); container.addEventListener('mousemove', (e) => { if(_this.objectMovingFlag){ return; } if (!isDown) return; e.preventDefault(); const x = e.pageX - container.offsetLeft; const y = e.pageY - container.offsetTop; const walkX = (x - startX) * 2; // 水平滚动速度 const walkY = (y - startY) * 2; // 垂直滚动速度 container.scrollLeft = scrollLeft - walkX; container.scrollTop = scrollTop - walkY; }); // 触摸事件支持 container.addEventListener('touchstart', (e) => { if(_this.objectMovingFlag){ return; } isDown = true; startX = e.touches[0].pageX - container.offsetLeft; startY = e.touches[0].pageY - container.offsetTop; scrollLeft = container.scrollLeft; scrollTop = container.scrollTop; }); container.addEventListener('touchend', () => { if(_this.objectMovingFlag){ return; } isDown = false; }); container.addEventListener('touchmove', (e) => { if(_this.objectMovingFlag){ return; } if (!isDown) return; const x = e.touches[0].pageX - container.offsetLeft; const y = e.touches[0].pageY - container.offsetTop; const walkX = (x - startX) * 2; const walkY = (y - startY) * 2; container.scrollLeft = scrollLeft - walkX; container.scrollTop = scrollTop - walkY; }); }, initScroll() { const scrollableDiv = document.getElementById('col-left'); const scrollableDiv1 = document.getElementById('app'); let startY1 = 0; let scrollTop1 = 0; // 定义 touchstart touchmove 处理函数 let startY = 0; let scrollTop = 0; this.touchStartHandler = (e) => { startY = e.touches[0].clientY; scrollTop = scrollableDiv.scrollTop; startY1 = e.touches[0].clientY; scrollTop1 = scrollableDiv1.scrollTop; }; this.touchMoveHandler = (e) => { e.preventDefault(); // 阻止默认滚动行为 const currentY = e.touches[0].clientY; const diff = currentY - startY; scrollableDiv.scrollTop = scrollTop - diff; const currentY1 = e.touches[0].clientY; const diff1 = currentY1 - startY1; scrollableDiv1.scrollTop = scrollTop1 - diff1; }; // 添加事件监听 document.addEventListener('touchstart', this.touchStartHandler); document.addEventListener('touchmove', this.touchMoveHandler, { passive: false }); }, removeScrollListeners() { // 移除事件监听 if (this.touchStartHandler) { document.removeEventListener('touchstart', this.touchStartHandler); } if (this.touchMoveHandler) { document.removeEventListener('touchmove', this.touchMoveHandler); } }, async init() { await this.getFileServerConfig(); this.initProperties(); // this.findSignByUserId(); // this.findCanUseSealList(); this.setPdfArea(); // 监听键盘事件 document.addEventListener("keydown", (event) => { // 检查按下的是否是删除Delete或Backspace) if (event.key === "Delete" || event.key === "Backspace") { const activeObject = this.canvasEle.getActiveObject(); if (activeObject) { // 删除活动对象 this.canvasEle.remove(activeObject); // 清空活动对象 this.canvasEle.setActiveObject(null); // 渲染画布以显示更改 this.canvasEle.renderAll(); } } }); // 我的表单新增时,只有签名 if (this.isOwnSign) { this.menuTree[0].isOwnSign = true; } // 执行页码 this.pageNumber(); }, pageNumber() { this.currentPage = this.pageN; this.cutover(); }, /** * @description: 设置PDF预览区域内容 * @return {*} */ async setPdfArea() { let pdfUrl = ""; if (!this.fileObj.fileId || this.fileObj.fileId === '') { this.$toast("没有需处理的文件"); return; } if (this.isPdf()) { const pdfFile = this.$STDCOMMON.fileOperation.base64ToFile( this.fileObj.content, this.fileName, this.mimeType ); pdfUrl = URL.createObjectURL(pdfFile); } else { this.showLoadingChild = true; await this.getFile(); this.showLoadingChild = false; if (!this.previewFile) { this.pageTotal = 0; return; } pdfUrl = URL.createObjectURL(this.previewFile); } this.$nextTick(() => { this.showpdf(pdfUrl); }); }, /** * @description: 渲染pdf,到时还会盖章信息,在渲染时,同时显示出来,不应该在切换页码时才显示印章信息 * @return {*} */ showpdf(pdfUrl) { this.canvas = document.getElementById("the-canvas"); this.ctx = this.canvas.getContext("2d"); this.pdfUrl = pdf.createLoadingTask({ url: pdfUrl, // 解决pdf文件,后台填充的文本不显示问题,与字体有关 CMapReaderFactory, }); this.pdfUrl.promise .then((pdfDoc_) => { this.pdfDoc = pdfDoc_; this.numPages = this.pdfDoc.numPages; this.renderPage(this.pageNum).then(() => { const pdfContainer = document.getElementById('col-left'); this.renderPdf({ width: pdfContainer.clientWidth, height: pdfContainer.clientHeight, }); this.commonSign(this.pageNum, true); }); }) .catch((err) => { this.pageTotal = 0; // this.showLoading = false; }); }, // 放大 zoomIn() { this.currentScale = Math.min(this.currentScale + this.scaleStep, this.maxScale); this.applyZoom(); }, // 缩小 zoomOut() { this.currentScale = Math.max(this.currentScale - this.scaleStep, this.minScale); this.applyZoom(); }, // 应用缩放 async applyZoom() { document.getElementById('canvas-div').style.transform = `scale(${this.currentScale})`; }, /** * @description: 初始化当前页的PDF内容,canvas宽度高度 * @param {*} num 当前页 * @return {*} */ renderPage(num) { let _this = this; this.pageRendering = true; return this.pdfDoc.getPage(num).then((page) => { // 获取目标 div 的宽度 const pdfContainer = document.getElementById('col-left'); const containerWidth = pdfContainer.clientWidth; // 获取页面视图的原始尺寸 const originalViewport = page.getViewport({ scale: 1 }); // 计算适应 div 宽度的缩放比例 const margin = 10; // 边距设置 this.mobileScale = (containerWidth) / originalViewport.width; // 重新获取视口,根据计算的 scale let viewport = page.getViewport({ scale: this.scale }); //设置视口大小 scale数值越大,清晰度越高,占用内存越高 _this.canvas.height = viewport.height; _this.canvas.width = viewport.width; _this.canvasEle && _this.canvasEle.setHeight(viewport.height); _this.canvasEle && _this.canvasEle.setWidth(viewport.width); let renderContext = { canvasContext: _this.ctx, viewport: viewport, }; let renderTask = page.render(renderContext); renderTask.promise.then(() => { _this.pageRendering = false; if (_this.pageNumPending !== null) { this.renderPage(_this.pageNumPending); _this.pageNumPending = null; } }); }); }, /** * @description: 设置绘图区域宽高 * @return {*} */ renderPdf(data) { this.whDatas = data; }, queueRenderPage(num) { if (this.pageRendering) { this.pageNumPending = num; } else { this.renderPage(num); } }, /** * @description: 翻页展示盖章信息 * @param {*} pageNum 第几页 * @param {*} isFirst 是否第一页 * @return {*} */ commonSign(pageNum, isFirst = false) { if (isFirst == false) this.canvasEle.remove(this.canvasEle.clear()); //清空页面所有签章 if (!this.signs || !Object.keys(this.signs).length) return false; let datas = this.signs[pageNum]; if (datas != null && datas != undefined) { for (let index in datas) { const obj = datas[index]; const params = { sealUrl: obj.type === "i-text" ? obj.text : obj.sealUrl, left: obj.left , top: obj.top , index: obj.index, scaleX: obj.scaleX, scaleY: obj.scaleY, type: obj.type, useId: obj.useId, fileId: obj.fileId, hasControls: obj.type === SIGN, angle:obj.angle || 0, selectable: obj?.selectable, creatorId: obj?.creatorId, creatorName: obj?.creatorName, createTime: obj?.createTime, writeSignBase64: obj?.writeSignBase64 }; this.addSeal(params); } } }, /** * @description: 生成绘图区域,并设置绘图区域的宽高,位置与预览的canvas重叠 * @return {*} */ renderFabric() { let canvaEle = document.querySelector("#ele-canvas"); const thecanvas = document.querySelector("#the-canvas"); canvaEle.width = document.querySelector("#the-canvas").clientWidth; canvaEle.height = document.querySelector("#the-canvas").clientHeight; this.canvasEle = new fabric.Canvas(canvaEle); let container = document.querySelector(".canvas-container"); container.style.position = "absolute"; container.style.top = "3px"; // container.style.left = (width - document.querySelector("#the-canvas").clientWidth) / 2 - 3 + "px"; this.pageNumber(); }, /** * @description: 相关事件操作,比如在绘图区域点击事件,元素的拖拽事件 * @return {*} */ canvasEvents() { let _this = this; // 鼠标在绘图区域的点击事件,鼠标按下时,如果在绘制的对象上,则使对象进入编辑状态 this.canvasEle.on("mouse:down", (options) => { var activeObject = options.target; if (activeObject && activeObject?.selectable) { _this.objectMovingFlag = true; } if (activeObject && activeObject.isEditing) { return; } if (activeObject && activeObject.type === "textbox") { activeObject.enterEditing(); activeObject.selectAll(); } this.canvasEle.renderAll(); }); // 拖拽边界 不能将图片拖拽到绘图区域外 this.canvasEle.on("object:moving", function (e) { _this.objectMovingFlag = true; var obj = e.target; // if object is too big ignore if ( obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width ) { return; } obj.setCoords(); // top-left corner if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) { obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top); obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left); } // bot-right corner if ( obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width ) { obj.top = Math.min( obj.top, obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top ); obj.left = Math.min( obj.left, obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left ); } }); // 监听所有对象的拖动结束 this.canvasEle.on('object:modified', function(e) { _this.objectMovingFlag = false ; }); }, /** * @description: 拖拽时添加fabric对象,签名,印章,文本等 * @param {*} sealUrl 如果是图片,则是图片的url,如果是文本,则是文本内容 * @param {*} left 左间距 * @param {*} top 顶部间距 * @param {*} index 索引:mainImagelist的 * @param {*} scaleX x轴比例尺 * @param {*} scaleY y轴比例尺 * @param {*} type 类型:TEXT,SIGN,STAMP * @param {*} useId 签名id,或者印章id * @param {*} fileId 签名或印章的文件id * @param {*} hasControls 是否支持编辑 * @param {*} isInit * @return {*} sealUrl, left, top, index, scaleX, scaleY, type, useId, fileId, hasControls = true, isInit = false */ addSeal(params) { // 缩放比例 const scaling = this.mobileScale / 2; const scrollableDiv = document.getElementById('canvas-div'); const scrollTop = scrollableDiv.scrollTop; // 签名印章使用图片元素 fabric.Image.fromURL(params.sealUrl, (oImg) => { oImg.hasControls = params.hasControls; oImg.set({ left: params.left * scaling, top: (params.top + scrollTop) * scaling, scaleX: params.scaleX || 1, scaleY: params.scaleY || 1, index: params.index, hasControls: params.hasControls, type: params.type, useId: params.useId, fileId: params.fileId, sealUrl:params.fileId ? `${this.fileUrl}/dl/view/${params.fileId}`:params?.writeSignBase64, pageNum: this.pageNum, businessId: this.entity.id, sealSignId: params.sealSignId, pageWidth: this.canvas.width, pageHeight: this.canvas.height, newFieldFlag: params.newFieldFlag, selectable: params.selectable, creatorId: params?.creatorId, creatorName: params?.creatorName, createTime: params?.createTime, writeSignBase64: params?.writeSignBase64 }); oImg.lockRotation = true; // 禁止旋转 // 签名禁止左右上下拖动,只支持按比例拖动 oImg.setControlVisible("mt", false); oImg.setControlVisible("mb", false); oImg.setControlVisible("ml", false); oImg.setControlVisible("mr", false); oImg.set({ angle: params.angle }); // oImg.scale(0.5); //图片缩小一 if(params.type === STAMP){ oImg.scale(0.38); } else { oImg.scale(scaling * params.scaleX); } this.$nextTick(()=>{ this.canvasEle && this.canvasEle.add(oImg); }) }); }, getSealSignPositionFieldList() { const data = this.confirmSignature(); const result = []; for (const key in data) { const cache = data[key]; if (JSON.stringify(cache) === "{}") { continue; } for (const k in cache) { const ele = cache[k]; if (JSON.stringify(ele) === "{}") { continue; } result.push(ele); } } if (result && result.length) { const scaling = this.mobileScale / 2; result.forEach(i => { // if (i.type === SIGN) { this.$set(i, 'scaleX', i.scaleX / scaling); this.$set(i, 'scaleY', i.scaleY / scaling); // this.$set(i, 'pageHeight', i.pageHeight / scaling); this.$set(i, 'bly', i.bly / scaling); // } }) } return result; }, /** * @description: 判断是否是pdf文件 * @return {*} */ isPdf() { if (this.fileObj?.fileName) { const arr = this.fileObj?.fileName.split("."); this.fileType = arr[arr.length - 1]; } return this.fileType && this.fileType.toLowerCase() === "pdf"; }, async getFile() { return await getPdfFile({ id: this.fileObj?.id, fileId: this.fileObj?.fileId, fileName: this.fileObj?.fileName, type: this.fileType, content: this.fileObj?.content, }) .then((res) => { if (!res.success) { this.previewFile = null; return; } if (!res.data) { this.previewFile = null; return; } this.previewFile = this.$STDCOMMON.fileOperation.base64ToFile( res.data, this.fileName, this.mimeType ); }) .catch(() => { this.previewFile = null; }); }, /** * @description: 上一页点击事件:确认签章位置等属性并保存到缓存 * @return {*} */ prevPage() { this.confirmSignature(); if (this.pageNum <= 1) { return; } this.pageNum--; this.currentPage = this.pageNum; this.$emit('page', this.currentPage); }, /** * @description: 下一页点击事件:确认签章位置等属性并保存到缓存 * @return {*} */ nextPage() { this.confirmSignature(); if (this.pageNum >= this.numPages) { return; } this.pageNum++; this.currentPage = this.pageNum; this.$emit('page', this.currentPage); }, /** * @description: 跳转按钮事件:确认签章位置等属性并保存到缓存 * @return {*} */ cutover() { this.confirmSignature(); this.pageNum = this.currentPage; if (!this.currentPage) { this.pageNum = 1; this.currentPage = 1; } this.$emit('page', this.currentPage); }, /** * @description: 确认签章位置并保存到缓存 * @return {*} */ confirmSignature() { let data = this.canvasEle.getObjects(); //获取当前页面内的所有签章信息 let caches = JSON.parse(JSON.stringify(this.signs)); //获取缓存字符串后转换为对象 let signDatas = {}; //存储当前页的所有签章信息 let i = 0; for (var val of data) { signDatas[i] = { width: val.width, // 签字,盖章,文本元素的宽度高度 height: val.height, top: val.top / (this.mobileScale / 2), // 据顶部距离 left: val.left / (this.mobileScale / 2), // 距左侧距离 angle: val.angle, translateX: val.translateX, translateY: val.translateY, scaleX: val.scaleX, // 比例尺x scaleY: val.scaleY, // 比例尺y pageNum: val.pageNum, // pdf第几页 sealUrl: val.sealUrl, // 签字或印章文件的地址 useId: val.useId, // 签字或印章数据的id fileId: val.fileId, // 签字或印章的文件id index: val.index, // 索引,目前用不到 type: val.type, // 元素类型:i-text, image type: val.type, // 类型,签字,盖章,文本 businessId: val.businessId, // 表单id sealSignId: val.sealSignId, // 印章/签名对应的业务id hasControls: val.hasControls, // 是否可编辑,拖动操作 text: val.text, // 如果是文本:文本内容 pageWidth: val.pageWidth, // 页面宽度 pageHeight: val.pageHeight, // 页面高度 tlx: val.aCoords.tl.x, //左上角x tly: val.aCoords.tl.y, // 左上角y trx: val.aCoords.tr.x, // 右上角x try: val.aCoords.tr.y, // 右上角y blx: val.aCoords.bl.x + 3, // 左下角x bly: val.aCoords.bl.y, // 左下角y brx: val.aCoords.br.x, // 右下角x bry: val.aCoords.br.y, // 右下角 y newFieldFlag: val?.newFieldFlag, scale:this.scale, selectable: val.selectable, // 是否可拖动 creatorId: val?.creatorId, creatorName: val?.creatorName, createTime: val?.createTime, writeSignBase64: val?.writeSignBase64 }; i++; } if (caches == null) { caches = {}; caches[this.pageNum] = signDatas; } else { caches[this.pageNum] = signDatas; } this.signs = caches; return this.signs; }, initProperties() { this.pageNum = 1; this.numPages = 1; this.pdfUrl = ""; this.whDatas = null; // 处理默认数据,只能推拽自己的盖章签字 let sealSignPositionFieldMap = this.sealSignPositionFieldMap; if (sealSignPositionFieldMap) { Object.keys(sealSignPositionFieldMap).forEach((key) => { let list = sealSignPositionFieldMap[key]; list.forEach((item) => { item.selectable = item.creatorId === this.userInfo.id; if(!item.fileId && item?.writeSignBase64){ item.sealUrl = item?.writeSignBase64; } }); }); } this.signs = sealSignPositionFieldMap; }, removeSignature() { this.canvasEle.remove(this.canvasEle.getActiveObject()); }, /** * @description: 拖动结束,判断是否在指定的区域,如果在预览区域内,则将印章放进去,否则不绘制 * @param {*} e * @return {*} */ end(e, obj) { const canvasDiv = this.$refs.canvasDiv; // 指定区域的DOM元素 const rect = canvasDiv.getBoundingClientRect(); const scrollableDiv = document.getElementById('canvas-div'); const scrollTop = scrollableDiv.scrollTop; const scrollLeft = scrollableDiv.scrollLeft; if ( e.originalEvent.changedTouches[0].pageX >= rect.left && e.originalEvent.changedTouches[0].pageX <= rect.right && e.originalEvent.changedTouches[0].pageY >= rect.top && e.originalEvent.changedTouches[0].pageY <= rect.bottom ) { // end元素中取originalEvent 里边有4组xy,多试一下该用哪个 const type = obj.type; let params = { sealUrl: obj.url, left: e.originalEvent.changedTouches[0].clientX + scrollLeft, top: e.originalEvent.changedTouches[0].clientY, index: e.newDraggableIndex, scaleX: 1, scaleY: 1, type: type, useId: obj.useId, fileId: obj.fileId, sealSignId: obj.sealSignId, hasControls: type === SIGN, isInit: true, newFieldFlag: "1", angle:obj.angle || 0, selectable: true, // false禁止选择,从而禁止拖动 }; this.addSeal(params); } else { this.$toast("请将个人签名或电子印章拖入指定区域内"); } }, /** * @description: 查询当前登录人的签名信息,并处理成需要的格式 * @return {*} */ findSignByUserId() { return findSignListByUserId(this.userInfo.id) .then((res) => { if (!res.success || !res.data || !res.data.length) { return; } res.data = res.data.filter(item => { return item.fileId != null && item.fileId !== ''; }); if (!res.success || !res.data || !res.data.length) { return; } let list = []; res.data.forEach((item) => { list.push({ url: item.content, useId: this.userInfo.id, fileId: item.fileId, type: SIGN, pid: SIGN, sealSignId: item.businessId, hasControls: true, }); }); this.menuTree[1].children = list; this.mainImagelist.push(...list); }) .catch((err) => { this.$message.error(err); }); }, findCanUseSealList() { findCanUseSealList({ userId: this.userInfo.id, moduleCode: 'smartsFormCenterAudit', }) .then((res) => { if (!res.success || !res.data) { return; } let list = []; res.data.forEach((item) => { list.push({ url: item.file.content, useId: this.userInfo.id, fileId: item.file.fileId, type: STAMP, pid: STAMP, sealSignId: item.id, hasControls: false, sealName: item.sealName, }); }); if (!this.isOwnSign) { this.menuTree[0].children = list; this.mainImagelist.push(...list); } }) .catch(() => { // this.showLoading = false; }); }, getFileServerConfig() { return getFileServerConfig() .then((res) => { if (!res.success || !res.data) { return; } this.fileUrl = res.data.fileServerUrl; }) .catch(() => { // this.showLoading = false; }); }, nullity() { if (!this.currentPage) { this.pageNum = 1; this.currentPage = 1; } }, }, beforeDestroy() { this.removeScrollListeners(); }, }; </script> <style lang="scss" > .drag-sign-seal { .el-tree { overflow: auto; } .el-row { border: 0px solid rgba(190, 197, 208, 1); border-radius: 2px; } .col-left { flex-direction: column; text-align: center; .el-input__inner { width: 100% !important; } } .col-right { display: none !important; border-left: 1px solid rgba(190, 197, 208, 1); height: 100%; padding: 15px; .el-tree { margin-top: 10px; height: calc(100% - 40px); } .custom-tree-node { height: 26px !important; } .el-tree-node__content { height: auto; padding-left: 0 !important; } } /*pdf部分*/ .pCenter { display: block; height: 100%; // overflow: scroll; //overflow-x: auto; //overflow-y: auto; // &::-webkit-scrollbar { // width: 0.12rem !important; // height: 0.12rem !important; // } // &::-webkit-scrollbar-thumb { // border-radius: 6px; // background-color: #dedede; // } // &::-webkit-scrollbar-track { // border-radius: 6px; // background-color: rgba(0, 0, 0, 0); // } } #the-canvas { // margin-top: 10px; border: 0px solid #f0f0f0; } #ele-canvas { border: 0px solid #f0f0f0; } html:fullscreen { background: white; } .elesign { display: flex; flex: 1; flex-direction: column; position: relative; margin: auto; height: 100%; } .page { display: flex; align-items: center; justify-content: center; text-align: center; margin: 0 auto; margin-top: 1%; margin-bottom: 1%; display: flex; .el-button--small span { color: #444 !important; } .el-input__inner { width: 0.6rem; text-align: center; } } .canvas-div-box { width: 100%; height: 100%; overflow-x: auto; overflow-y: auto; -webkit-overflow-scrolling: touch; /* 启用iOS惯性滚动 */ scrollbar-width: none; /* 隐藏Firefox滚动条 */ // &::-webkit-scrollbar { // -webkit-appearance: none; // width: 8px; /* 垂直滚动条宽度 */ // height: 8px; /* 水平滚动条高度 */ // } // &::-webkit-scrollbar-thumb { // background-color: rgba(0, 0, 0, 0.3); // border-radius: 4px; // } // &::-webkit-scrollbar-track { // background-color: rgba(0, 0, 0, 0.05); // } } .canvas-div { width: 100%; margin: 0; overflow-y: hidden; overflow-x: hidden; // height: 100%; position: relative; text-align: left; float: left; transform-origin: 0 0; } #col-left-box { height: 94%; } @keyframes ani-demo-spin { from { transform: rotate(0deg); } 50% { transform: rotate(180deg); } to { transform: rotate(360deg); } } li { list-style-type: none; padding: 10px; } .imgstyle { vertical-align: middle; width: 130px; // border: solid 1px #e8eef2; // background-image: url("./img/rank.png"); background-repeat: no-repeat; } .btn-outline-default { color: #444; background-color: transparent; background-image: none; border: 1px solid #dcdfe6; margin-left: 0.1rem; } .btn-outline-default:hover { color: #444; // border-color: #009994; background-color: #ecfbf8; } .draggable-list { height: calc(100% - 50px); margin-bottom: 5px; &::-webkit-scrollbar { width: 0px; height: 6px; } } .lock { div.lock-icon { position: absolute; bottom: 0px; right: 0px; vertical-align: middle; width: 20px; height: 20px; // background-image: url("./img/lock.png"); background-repeat: no-repeat; cursor: pointer; } img { cursor: not-allowed; background-color: rgba(232, 238, 242, 0.7); /* 半透明遮罩 */ z-index: 10; } } .item { position: relative; } .unlock { & > span { &:hover { background-color: rgba(0, 153, 148, 0.05); cursor: move; } } } .nodrag-tag { cursor: pointer; } .date-item { text-align: center; width: 160px; height: 50px; line-height: 50px; background-color: transparent; &:hover { cursor: move; } } } .landscape .van-tree-select__nav { height: 2rem; } .landscape .van-tree-select__content { height: 2rem; } .landscape .canvas-div { height: 11rem!important; margin: 0!important; } .landscape .col-left-box { padding-right: 0.4rem; overflow: auto; height: 2rem; } .landscape .page { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; text-align: center; margin-top: -0.01rem; /* margin-top: 0.5%; */ margin-bottom: 0.5%; display: flex; font-size: 0.16rem; position: fixed; /* margin-top: 3rem; */ /* top: 3rem; */ z-index: 3; background: white; width: 100%; /* height: 0.2rem; */ /* top: 0.5rem;*/ .el-button--small span { color: #444 !important; } } </style> 这是个移动端的签字盖章的组件,现在有个问题,当我翻页的时候,上一页的签名会缩小
07-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值