JavaScript: 解决了<el-image>频繁加载图片出现白色边框问题

优化图片渲染:双缓存解决canvas闪屏问题与图片缩放技巧
本文探讨了在频繁图片切换中遇到的canvas闪屏问题,通过引入双缓存技术解决清空画布导致的视觉延迟。同时,介绍了如何将图片缩放至canvas大小的方法,以提高用户体验。

问题描述

当频繁加载图片并渲染的时候,出现加载渲染图片的控件在两张图片切换的过程中出现白色闪烁的问题。

问题排查

查看源代码后,发现html中用 元素来显示图片的。 并且通过改变:src="jpgPath"的值来替换图片。
在这里插入图片描述
在这里插入图片描述

解决方法:

临时解决方法: 用canvas 绘制图片的方式来代替 。
新问题: 1. canvas 直接不断的绘制image,会出现闪屏的问题。
解决方法:使用双缓存解决 Canvas clearRect 引起的闪屏问题 : 使用双缓存解决问题

updateCanvas(){
    const canvas = document.getElementById('canvas'); // 获取页面中的 canvas
    const ctx = canvas.getContext('2d');
    
    const tempCanvas = document.createElement('canvas'); // 新建一个 canvas 作为缓存 canvas
    const tempCtx = tempCanvas.getContext('2d');
    tempCanvas.width = 1448; tempCanvas.height = 750; // 设置宽高
 
    // 开始绘制
    tempCtx.drawImage(bg,0,0); // 背景
    ... // 省略其他绘制过程
    
    // 缓存 canvas 绘制完成
    
    ctx.clearRect(0,0,1448,750); // 清空旧 canvas
    ctx.drawImage(tempCanvas,0,0); // 将缓存 canvas 复制到旧的 canvas
}

总结
重绘画布的时候,我们需要使用 clearRect 来清空画布,此时的画布是空的,开始重绘后,如果内容较多,时间也就相应的增加,因此视觉出现了空档期,我们就看到了闪屏的情况;
解决闪屏,其实就是怎么解决绘制时间较长的问题;
这里参考了图形图象处理编程中 双缓存 的概念,将绘制过程交给了 缓存 canvas,这样页面中的 canvas 就省去了绘制过程,而 缓存 canvas 并没有添加到页面,所以我们就看不到绘制过程,也就解决了闪屏的问题。

问题2 : 如何把图片缩放到canvas的大小
解决方法:在drawImage后面多添加两个参数值:canvas.width,canvas.height 就可以了。

 tempCtx.drawImage(img1,0,0,canvas.width,canvas.height);
 ctx.clearRect(0,0,canvas.width,canvas.height);
 ctx.drawImage(tempCanvas,0,0,canvas.width,canvas.height);

最终代码:

在这里插入图片描述

updateCanvas(jpgPath) {
                const canvas = document.getElementById('canvasImage');
                const ctx = canvas.getContext('2d');
                //canvas.width = 400;
                //canvas.height = 400;
                //canvas.style.border = '1px solid #ccc';

                const tempCanvas = document.createElement('canvas');
                const tempCtx = tempCanvas.getContext('2d');
                //tempCanvas.width = 400;
                //tempCanvas.height = 400;

                var img1 = new Image();
                img1.src= jpgPath;//只要设置了src属性,当前img对象立即去加载图片。
                //第二步,图片加载完成后,把图片绘制到canvas上
                img1.onload = function() {
                    tempCtx.drawImage(img1,0,0,canvas.width,canvas.height);

                    ctx.clearRect(0,0,canvas.width,canvas.height);
                    ctx.drawImage(tempCanvas,0,0,canvas.width,canvas.height);
                   
                };

                


            },
  // 获取当前帧的jpg路径
            getPicPath() {
                let _this = this;
                axios.get(this.apiUrl + '/get_frame_jpg')
                    .then(function (response) {
                        console.log(response.data.data.pcdPath);
                        _this.jpgPath = _this.pcdUrl + response.data.data.jpgPath
                        
                        _this.updateCanvas(_this.jpgPath);  //核心,就是改为canvas绘制图片。
                    })
                    .catch(function (error) {
                         console.log(2,error);
                    });
            },
<template> <div style="padding: 10px"> <div style="margin: 10px 0"> <!-- 功能区域--> <el-button type="primary" round v-on:click="add">新增</el-button> <!-- 搜索区域--> <el-button type="primary" icon="el-icon-search" style="margin-outside: 10px; float: right" @click="goSearch" >搜索</el-button > <el-input v-model="search" placeholder="请输入你搜索的产品的标题" style="width: 20%; float: right" clearable ></el-input> </div> <!-- 表格区域--> <el-table :data="tableData" stripe border v-loading="loading" style="width: 99%" > <el-table-column prop="id" label="产品ID" sortable> </el-table-column> <el-table-column prop="title" label="产品标题"> </el-table-column> <el-table-column prop="sellPoint" label="卖点" show-overflow-tooltip> </el-table-column> <el-table-column prop="price" label="价格"> </el-table-column> <el-table-column label="产品图片"> <template #default="scope"> <el-image style="width: 100px; height: 100px" :src="scope.row.image" :preview-src-list="[scope.row.image]" > </el-image> </template> </el-table-column> <el-table-column prop="stock" label="库存数量"> </el-table-column> <el-table-column prop="created" sortable label="创建间" width="150px"> </el-table-column> <el-table-column prop="updated" sortable label="更新间" width="150px"> </el-table-column> <!-- 操作区域--> <el-table-column fixed="right" label="操作" align="center" width="150"> <template #default="scope"> <!-- 这里scope.row传入直接是传入一行的数据对象,可以使用row.username取出用户名的--> <el-button v-on:click="editeClick(scope.row)" type="primary" size="small" >编辑</el-button > <el-popconfirm title="确定要删除这个产品吗?" @confirm="deleteClick(scope.row.id)" style="margin-left: 10px" > <template #reference> <el-button type="danger" size="small">删除</el-button> </template> </el-popconfirm> </template> </el-table-column> </el-table> <!-- 页码区域以及弹出表单--> <div style="margin: 20px 0"> <!-- 页码区域,在Vue2里面是不支持v-model:prop="data"的,所以我们要用v-bind:prop.sync="data"实现双向绑定--> <!-- <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="currentPage" :page-sizes="[5, 10, 20]" :page-size.sync="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="totalNumber" > </el-pagination> --> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[2, 3, 5, 10]" :page-size="currentSize" layout="total, sizes, prev, pager, next, jumper" :total="currentTotal" > </el-pagination> <!-- 添加产品的表单--> <el-dialog title="新增/修改产品" :visible.sync="dialogVisible" width="40%" > <!-- 输入表单--> <el-form v-bind:model="form" label-width="120px"> <el-form-item label="产品标题:"> <el-input v-model="form.title" style="width: 80%"></el-input> </el-form-item> <el-form-item label="价格:"> <el-input v-model="form.price" style="width: 80%"></el-input> </el-form-item> <el-form-item label="库存数量:"> <el-input v-model="form.stock" style="width: 80%"></el-input> </el-form-item> <el-form-item label="产品类目:"> <!-- <el-input v-model="form.cid" style="width: 80%"></el-input> --> <el-cascader :props="customProps" v-model="selectOptions" :options="options" clearable ref="cascader" @change="listenOption()" ></el-cascader> </el-form-item> <el-form-item label="产品状态:"> <el-radio v-model="form.status" :label="1">正常</el-radio> <el-radio v-model="form.status" :label="2">下架</el-radio> <el-radio v-model="form.status" :label="3">删除</el-radio> </el-form-item> <el-form-item label="卖点:"> <el-input type="textarea" v-model="form.sellPoint" style="width: 80%" ></el-input> </el-form-item> <!-- <el-form-item label="产品图片url:"> <el-input type="textarea" v-model="form.image" style="width: 80%"></el-input> </el-form-item> --> <div style=" width: 178px; height: 178px; margin-bottom: 20px; margin-left: 250px; " > <el-upload class="avatar-uploader" action="http://localhost:9090/files/upload" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload" > <img v-if="avatar" :src="avatar" class="avatar" alt="产品图片" /> <i v-else class="el-icon-plus avatar-uploader-icon"></i> </el-upload> </div> </el-form> <!-- 下面的按钮--> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false">取 消</el-button> <el-button type="primary" @click="save">确 定</el-button> </span> </el-dialog> </div> </div> </template> <script> // @ is an alias to /src import request from "@/utils/request"; export default { name: "ItemView", components: {}, created() { document.title = "产品管理"; this.loadData(); this.getalltype(); }, methods: { handleSizeChange(val) { this.currentSize = val; this.loadData(); }, handleCurrentChange(val) { this.currentPage = val; this.loadData(); }, listenOption() { let obj = this.$refs["cascader"].getCheckedNodes(); if (obj.length > 0 && obj[0] && obj[0].data) { // console.log("选中的行政区 obj.data=", obj[0].data.id); this.tid = obj[0].data.id; this.form.tid = obj[0].data.id; } else { console.log("请选择"); this.tid = ""; this.form.tid = ""; } }, getalltype() { request .get("/type/findAllType", { params: {}, }) .then((res) => { this.options = this.getTreeData(res.data); }); }, getTreeData(data) { // 循环遍历json数据 for (var i = 0; i < data.length; i++) { if (data[i].children.length < 1) { // children若为空数组,则将children设为undefined data[i].children = undefined; } else { // children若不为空数组,则继续 递归调用 本方法 this.getTreeData(data[i].children); } } return data; }, handleAvatarSuccess(res) { this.avatar = res.data; this.form.image = res.data; }, beforeAvatarUpload(file) { // const isJPG = file.type === 'image/jpeg'; const isLt2M = file.size / 1024 / 1024 < 10; // if (!isJPG) { // this.$message.error('上传头像图片只能是 JPG 格式!'); // } if (!isLt2M) { this.$message.error("上传头像图片大小不能超过 10MB!"); } return isLt2M; }, // 搜索功能 goSearch: function () { this.loadData(); }, // 加载表格数据,Get请求,Get请求不能直接传对象,只有Post可以直接传对象进去 loadData() { this.loading = true; request .get("/item", { params: { pageNum: this.currentPage, pageSize: this.currentSize, search: this.search, tid: this.tid, }, }) .then((res) => { console.log(res); this.tableData = res.data.records; this.currentTotal = res.data.total; this.loading = false; }); }, // 打开表单栏 add() { // 先清空表单 this.form = {}; // 再打开表单 this.dialogVisible = true; }, // 点击表单的确定按钮,保存数据到后台,Post请求和Put请求 save() { this.$message({ type: "info", message: "马上保存/修改", }); if (this.form.id) { // 有ID就更新 request.put("/item", this.form).then((res) => { console.log(res); if (res.code === "0") { this.$message({ type: "success", message: "更新成功", }); } else { this.$message({ type: "error", message: res.msg, }); } }); } else { // 没有就新增 // 这里不用添加/api,在request.js中我们已经添加了baseurl为api了,然后在vue.config.js中会被拦截 request.post("/item", this.form).then((res) => { console.log(res); if (res.code === "0") { this.$message({ type: "success", message: "新增成功", }); } else { this.$message({ type: "error", message: res.msg, }); } }); } // alert("保存/修改成功"); 需要检查之后再看是否成功 // 更新tableData的数据 this.loadData(); this.dialogVisible = false; }, // 修改行数据 editeClick(row) { // 将数据深拷贝过来,避免浅拷贝的修改问题 this.form = JSON.parse(JSON.stringify(row)); this.selectOptions = row.tid; this.avatar = row.image; this.dialogVisible = true; }, // 删除数据 deleteClick(id) { request .delete("/item", { params: { id: id, }, }) .then((res) => { if (res.code === "0") { this.$message({ type: "warning", message: "删除成功", }); } else { this.$message({ type: "error", message: res.msg, }); } }); // 更新数据 this.loadData(); }, // // 表单每页大小的改变,因为有了双向绑定,直接更新一下数据就好了 // handleSizeChange() { // this.loadData(); // }, // // 改变当前页码 // handleCurrentChange: function () { // this.loadData(); // }, }, data() { return { tid: "", //级联下拉框 customProps: { value: "id", label: "typeName", children: "children", emitPath: false, }, options: [], selectOptions: [], //图片路径 avatar: "", // 表单加载中 loading: true, //新建一个表单,用来储存新增产品和修改产品的数据 form: {}, // 增加框是否可见 dialogVisible: false, // 总数据数 currentTotal: 400, // 当前页码 currentPage: 1, // 一页多少个 currentSize: 5, // 输入框的数据 search: "", // 表格的数据 tableData: [], }; }, }; </script> <style> .avatar-uploader .el-upload { border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; } .avatar-uploader .el-upload:hover { border-color: #409eff; } .avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 178px; height: 178px; line-height: 178px; text-align: center; } .avatar { width: 178px; height: 178px; display: block; } </style>
最新发布
10-30
<el-form :rules="formRules" :model="pageModel.form" label-width="120px" size="small"> <el-row :gutter="24"> <el-col :span="11"> <el-form-item label="姓名" prop="policeName" class="content"> <el-input v-model.trim="pageModel.form.policeName" :disabled="pageModel.pageInfo.formConfig.type == '2' "@compositionstart="isComposing = true" @compositionend="handleCompositionEnd" /> </el-form-item> </el-col> <el-col :span="11" v-if="pageModel.pageInfo.environmentShow === true"> <el-form-item label="性别" prop="sex" class="content"> <el-select v-model="pageModel.form.sex" placeholder="请选择" style="width: 100%" :disabled="pageModel.pageInfo.formConfig.type == '2' "> <el-option v-for="(item, index) in pageModel.pageInfo .sexType" :key="index" :label="item.dictLabel" :value="item.dictValue"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="11" v-if="pageModel.pageInfo.environmentShow === true"> <el-form-item label="身份证号" prop="idCard" class="content"> <el-input v-model="pageModel.form.idCard" :disabled="pageModel.pageInfo.formConfig.type == '2' " /> </el-form-item> </el-col> <el-col :span="11"> <el-form-item label="所属单位" prop="deptId" class="content"> <el-cascader :options="pageModel.listDept" v-model="pageModel.form.deptId" :props="{ children: 'children', label: 'deptName', value: 'deptId', checkStrictly: true, }" style="width: 100%" @change="changeHandel" :disabled="pageModel.pageInfo.formConfig.type == '2' " clearable /> </el-form-item> </el-col> <el-col :span="11"> <el-form-item label="警号" prop="policeCode" class="content"> <el-input v-model="pageModel.form.policeCode" :disabled="pageModel.pageInfo.formConfig.type == '2' " onkeyup="this.value=this.value.replace(/[^\w_]/g,'')" /> </el-form-item> </el-col> <el-col :span="11"> <el-form-item label="警种" prop="policeType" class="content"> <el-input v-model="pageModel.form.policeType" :disabled="pageModel.pageInfo.formConfig.type == '2' " /> </el-form-item> </el-col> <el-col :span="11"> <el-form-item label="接入方式" prop="pattern" class="content"> <el-select v-model="pageModel.form.pattern" placeholder="请选择" style="width: 100%" :disabled="pageModel.pageInfo.formConfig.type == '2' "> <el-option v-for="(item, index) in pageModel.pageInfo .type" :key="index" :label="item.label" :value="item.value"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="11"> <el-form-item label="单位信息" prop="shortName" class="content"> <el-input v-model="pageModel.form.shortName" :disabled="pageModel.pageInfo.formConfig.type == '2' " /> </el-form-item> </el-col> <el-col :span="11" v-if="pageModel.pageInfo.environmentShow === true"> <el-form-item label="职务" prop="post" class="content"> <el-input v-model="pageModel.form.post" :disabled="pageModel.pageInfo.formConfig.type == '2' " /> </el-form-item> </el-col> <el-col :span="11" v-if="pageModel.pageInfo.environmentShow === true"> <el-form-item label="手机号" prop="phone" class="content"> <el-input v-model="pageModel.form.phone" :disabled="pageModel.pageInfo.formConfig.type == '2' " /> </el-form-item> </el-col> <el-col :span="11" v-if="pageModel.pageInfo.environmentShow === true"> <el-form-item label="邮箱" prop="email" class="content"> <el-input v-model="pageModel.form.email" :disabled="pageModel.pageInfo.formConfig.type == '2' " /> </el-form-item> </el-col> <el-col :span="24" v-if="pageModel.pageInfo.environmentShow === true"> <!-- :disabled="" --> <el-form-item label="头像" prop="userPic" class="content"> <img class="tx" v-if="pageModel.pageInfo.formConfig.type == '2'" :src="pageModel.form.userPic" alt="" /> <el-upload class="upload-demo" action="" v-else v-model="pageModel.form.userPic" :http-request="uploadActionHandle" :before-upload="beforeAvatarUpload" :before-remove="beforeDeleteImage" :on-remove="deleteImageHandel" :file-list="pageModel.pageInfo.imageList" list-type="picture"> <el-button size="small" type="primary">点击上传 </el-button> </el-upload> </el-form-item> </el-col> <el-col :span="24" v-if="pageModel.pageInfo.environmentShow === true"> <el-form-item label="备注" prop="remark" class="content"> <el-input v-model="pageModel.form.remark" :disabled="pageModel.pageInfo.formConfig.type == '2' " type="textarea" :rows="3" /> </el-form-item> </el-col> </el-row> </el-form>/*------------该页面独有的样式文件------------*/ @import "../../../assets/systemColor"; .tx { width: 60px; height: 60px; } .el-tree { overflow: auto !important; height: calc(100% - 30px); } .pageWrap { display: flex; flex-direction: column; width: 100%; min-height: calc(100vh - 80px); padding: var(--global-item-distance); overflow: hidden; background: var(--page-wrap-background); } /*查询模块*/ .searchBox { padding: 20px 15px 0 15px; border-radius: 5px; background: #ffffff; } .listBox { flex: 1; display: flex; justify-content: space-between; align-items: center; padding: 15px; margin-top: var(--global-item-distance); border-radius: 5px; overflow: scroll; background: #ffffff; } .leftBox { width: 212px; height: 97%; border: 1px solid #dddddd; border-radius: 5px; padding: 10px; } .rightBox { //width: 1380px; width: 90%; height: 97%; margin-left: 20px; border: 1px solid #dddddd; border-radius: 5px; padding: 10px; } ::v-deep .el-dialog__header { padding: 20px 20px 10px; background-image: linear-gradient(#9cc9f6, #ffffff); } .dialog-header { padding-left: 10px; border-left: 3px solid #005aff; .el-form { width: 100%; display: flex; flex-wrap: wrap; .el-form-item { width: 50%; } } } 为什么输入框输入内容之后高度会变化,产生下面内容移动,有空白,怎么解决这个高度问题
07-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值