基于Vue的移动端图片裁剪组件 vue-crop

2023-10-28 23:10

本文主要是介绍基于Vue的移动端图片裁剪组件 vue-crop,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

最近公司做一个移动端的项目,前端用的是vue + vant,需求:上传证件照,需要裁剪图片,vant 的上传组件满足不了,所以自己上手开发一个。

图片:

是由 https://blog.csdn.net/ch834301/article/details/79963152 这个修改而来的,已经修改成了在单独的组件可以用了,详情请看前面的链接。

代码如下:

<template><div class="zyg-uploader__upload"><!-- 删除按钮icon --><iid="zyg-delete-icon"class="van-icon van-icon-clear van-uploader__preview-delete"></i><!-- 照相机icon --><iid="zyg-upload-icon"class="van-icon van-icon-photograph van-uploader__upload-icon"></i><!-- 上传中 --><div v-show="uploadLoading" class="van-uploader__mask"><div class="van-loading van-loading--circular van-uploader__loading"><span class="van-loading__spinner van-loading__spinner--circular"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div><div class="van-uploader__mask-message">上传中...</div></div><img :src="imgUrl" alt="" id="img" class="zyg-image__img" /><inputtype="file"id="fileId"class="zyg-uploader__input"accept="image/png,image/jpg,image/jpeg"@change="change($event)"/></div>
</template>
<script>
import Cropper from "cropperjs";
import Exif from "exif-js";
export default {props: {// 定义的宽高比widthRate: {type: Number,default: 1,},// 定义的宽高比heightRate: {type: Number,default: 1,},imgUrl: {type: String,default: function () {return "";},},uploadLoading: {type: Boolean,default: false,},},data() {return {};},mounted() {this.iconFun();},methods: {iconFun() {if (document.getElementById("img").src == "") {document.getElementById("zyg-upload-icon").style.display = "block";} else {document.getElementById("zyg-upload-icon").style.display = "none";}},change(event) {let self = this;let image = document.getElementById("img"); //预览对象let file = document.getElementById("fileId").files[0]; //获取文件流self.$emit("before-upload", file, function (flag) {// 上传之前校验if (flag == false) {return;}// 显示裁剪框self.clip(event, {resultObj: image,aspectWithRatio: Number(self.widthRate),aspectHeightRatio: Number(self.heightRate),});});},//初始化方法initilize(opt) {let self = this;this.options = opt;//创建domthis.createElement();this.resultObj = opt.resultObj;//初始化裁剪对象this.cropper = new Cropper(this.preview, {aspectRatio: opt.aspectWithRatio / opt.aspectHeightRatio, // 裁剪框比例  默认NaN   例如:: 1 / 1,//裁剪框比例 1:1// aspectRatio: 1/1,autoCropArea: opt.autoCropArea || 0.8,viewMode: 2,guides: true, // 是否在剪裁框上显示虚线cropBoxResizable: false, //是否通过拖动来调整剪裁框的大小cropBoxMovable: true, //是否通过拖拽来移动剪裁框。dragCrop: false,dragMode: "move", //‘crop’: 可以产生一个新的裁剪框3 ‘move’: 只可以移动3 ‘none’: 什么也不处理center: false, // 是否显示裁剪框中间的+zoomable: true, //是否允许放大图像。zoomOnTouch: true, //是否可以通过拖动触摸来放大图像。scalable: true, // 是否允许缩放图片// minCropBoxHeight: 750,// minCropBoxWidth: 750,background: false, // 容器是否显示网格背景checkOrientation: true,checkCrossOrigin: true,zoomOnWheel: false, // 是否允许鼠标滚轴缩放图片toggleDragModeOnDblclick: false,ready: function () {// console.log(self.cropper.rotate(90))if (opt.aspectRatio == "Free") {let cropBox = self.cropper.cropBox;cropBox.querySelector("span.cropper-view-box").style.outline ="none";self.cropper.disable();}},});},//创建一些必要的DOM,用于图片裁剪createElement() {//初始化图片为空对象this.preview = null;// <img src="../../assets/app/loading.gif">let str ='<div><img id="clip_image" src="originUrl"></div><button type="button" id="cancel_clip">取消</button><button type="button" id="clip_button">确定</button>';str +='<div class="crop_loading"><div class="crop_content"><div class="crop_text">修剪中...</div></div></div>';str +='<div class="crop_success"><div class="crop_success_text">上传成功</div></div></div>';let body = document.getElementsByTagName("body")[0];this.reagion = document.createElement("div");this.reagion.id = "clip_container";this.reagion.className = "container";this.reagion.innerHTML = str;//添加创建好的DOM元素body.appendChild(this.reagion);this.preview = document.getElementById("clip_image");//绑定一些方法this.initFunction();},//初始化一些函数绑定initFunction() {let self = this;this.clickBtn = document.getElementById("clip_button");this.cancelBtn = document.getElementById("cancel_clip");//确定事件this.addEvent(this.clickBtn, "click", function () {self.crop();});//取消事件this.addEvent(this.cancelBtn, "click", function () {self.destoried();});//清空input的值this.addEvent(this.fileObj, "click", function () {this.value = "";});},//外部接口,用于input['file']对象change时的调用clip(e, opt) {let self = this;this.fileObj = e.srcElement;let files = e.target.files || e.dataTransfer.files;if (!files.length) return false; //不是图片直接返回//调用初始化方法this.initilize(opt);//获取图片文件资源this.picValue = files[0];//去获取拍照时的信息,解决拍出来的照片旋转问题// Exif.getData( files[0] , function(){//   self.Orientation = Exif.getTag( files[0], 'Orientation');//   console.log(self.Orientation)// });//调用方法转成url格式this.originUrl = this.getObjectURL(this.picValue);//每次替换图片要重新得到新的urlif (this.cropper) {this.cropper.replace(this.originUrl);}},//图片转码方法getObjectURL(file) {let url = null;if (window.createObjectURL != undefined) {// basicurl = window.createObjectURL(file);} else if (window.URL != undefined) {// mozilla(firefox)url = window.URL.createObjectURL(file);} else if (window.webkitURL != undefined) {// webkit or chromeurl = window.webkitURL.createObjectURL(file);}return url;},//点击确定进行裁剪crop() {let self = this;let image = new Image();let croppedCanvas;let roundedCanvas;// Cropdocument.querySelector(".crop_loading").style.display = "block";setTimeout(function () {croppedCanvas = self.cropper.getCroppedCanvas();// RoundroundedCanvas = self.getRoundedCanvas(croppedCanvas);let imgData = roundedCanvas.toDataURL();image.src = imgData;//判断图片是否大于100k,不大于直接上传,反之压缩if (imgData.length < 100 * 1024) {self.resultObj.src = imgData;//图片上传self.postImg(imgData);} else {image.onload = function () {//压缩处理let data = self.compress(image, self.Orientation);self.resultObj.src = data;//图片上传self.postImg(data);};}// 判断图片如果显示,则把相机给隐藏掉if (self.resultObj.src !== "") {document.getElementById("zyg-upload-icon").style.display = "none";}// 调用后台接口,传参let params = {// content: self.resultObj.src, // 图片的base64码file: document.getElementById("fileId").files[0], //获取文件流};self.$emit("after-upload", params); // 调用后台接口}, 20);},//获取裁剪图片资源getRoundedCanvas(sourceCanvas) {let canvas = document.createElement("canvas");let context = canvas.getContext("2d");let width = sourceCanvas.width;let height = sourceCanvas.height;canvas.width = width;canvas.height = height;context.imageSmoothingEnabled = true;context.drawImage(sourceCanvas, 0, 0, width, height);context.globalCompositeOperation = "destination-in";context.beginPath();context.rect(0, 0, width, height);context.fill();return canvas;},//销毁原来的对象destoried() {let self = this;//移除事件this.removeEvent(this.clickBtn, "click", null);this.removeEvent(this.cancelBtn, "click", null);this.removeEvent(this.fileObj, "click", null);//移除裁剪框this.reagion.parentNode.removeChild(this.reagion);//销毁裁剪对象this.cropper.destroy();this.cropper = null;},//图片上传postImg(imageData) {// console.log(imageData)this.$emit("callback", imageData);//这边写图片的上传let self = this;self.destoried();// window.setTimeout( function () {//   document.querySelector('.crop_loading').style.display = 'none';//   document.querySelector('.crop_success').style.display = 'block';//   //裁剪完后摧毁对象//     self.destoried();// },3000)},//图片旋转rotateImg(img, direction, canvas) {//最小与最大旋转方向,图片旋转4次后回到原方向const min_step = 0;const max_step = 3;if (img == null) return;//img的高度和宽度不能在img元素隐藏后获取,否则会出错let height = img.height;let width = img.width;let step = 2;if (step == null) {step = min_step;}if (direction == "right") {step++;//旋转到原位置,即超过最大值step > max_step && (step = min_step);} else {step--;step < min_step && (step = max_step);}//旋转角度以弧度值为参数let degree = (step * 90 * Math.PI) / 180;let ctx = canvas.getContext("2d");switch (step) {case 0:canvas.width = width;canvas.height = height;ctx.drawImage(img, 0, 0);break;case 1:canvas.width = height;canvas.height = width;ctx.rotate(degree);ctx.drawImage(img, 0, -height);break;case 2:canvas.width = width;canvas.height = height;ctx.rotate(degree);ctx.drawImage(img, -width, -height);break;case 3:canvas.width = height;canvas.height = width;ctx.rotate(degree);ctx.drawImage(img, -width, 0);break;}},//图片压缩compress(img, Orientation) {let canvas = document.createElement("canvas");let ctx = canvas.getContext("2d");//瓦片canvaslet tCanvas = document.createElement("canvas");let tctx = tCanvas.getContext("2d");let initSize = img.src.length;let width = img.width;let height = img.height;//如果图片大于四百万像素,计算压缩比并将大小压至400万以下let ratio;if ((ratio = (width * height) / 4000000) > 1) {console.log("大于400万像素");ratio = Math.sqrt(ratio);width /= ratio;height /= ratio;} else {ratio = 1;}canvas.width = width;canvas.height = height;//        铺底色ctx.fillStyle = "#fff";ctx.fillRect(0, 0, canvas.width, canvas.height);//如果图片像素大于100万则使用瓦片绘制let count;if ((count = (width * height) / 1000000) > 1) {count = ~~(Math.sqrt(count) + 1); //计算要分成多少块瓦片//            计算每块瓦片的宽和高let nw = ~~(width / count);let nh = ~~(height / count);tCanvas.width = nw;tCanvas.height = nh;for (let i = 0; i < count; i++) {for (let j = 0; j < count; j++) {tctx.drawImage(img,i * nw * ratio,j * nh * ratio,nw * ratio,nh * ratio,0,0,nw,nh);ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);}}} else {ctx.drawImage(img, 0, 0, width, height);}//修复ios上传图片的时候 被旋转的问题if (Orientation != "" && Orientation != 1) {switch (Orientation) {case 6: //需要顺时针(向左)90度旋转this.rotateImg(img, "left", canvas);break;case 8: //需要逆时针(向右)90度旋转this.rotateImg(img, "right", canvas);break;case 3: //需要180度旋转this.rotateImg(img, "right", canvas); //转两次this.rotateImg(img, "right", canvas);break;}}//进行最小压缩// let ndata = canvas.toDataURL( 'image/jpeg' , 0.1);let ndata = canvas.toDataURL("image/png", 0.1);console.log("压缩前:" + initSize);console.log("压缩后:" + ndata.length);console.log("压缩率:" + ~~((100 * (initSize - ndata.length)) / initSize) + "%");tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;return ndata;},//添加事件addEvent(obj, type, fn) {if (obj.addEventListener) {obj.addEventListener(type, fn, false);} else {obj.attachEvent("on" + type, fn);}},//移除事件removeEvent(obj, type, fn) {if (obj.removeEventListener) {obj.removeEventListener(type, fn, false);} else {obj.detachEvent("on" + type, fn);}},},
};
</script>
<style scoped>
.zyg-uploader__upload {position: relative;display: -webkit-box;display: -webkit-flex;display: flex;-webkit-box-orient: vertical;-webkit-box-direction: normal;-webkit-flex-direction: column;flex-direction: column;-webkit-box-align: center;-webkit-align-items: center;align-items: center;-webkit-box-pack: center;-webkit-justify-content: center;justify-content: center;box-sizing: border-box;width: 80px;height: 80px;margin: 0 8px 8px 0;background-color: #f7f8fa;border-radius: 8px;
}.zyg-uploader__input {position: absolute;top: 0;left: 0;width: 100%;height: 100%;overflow: hidden;cursor: pointer;opacity: 0;z-index: 99;
}.zyg-image__img {object-fit: cover;display: block;width: 100%;height: 100%;
}
#zyg-upload-icon {position: absolute;top: 50%;left: 50%;-webkit-transform: translate(-50%, -50%);transform: translate(-50%, -50%);
}
#zyg-delete-icon {display: none;position: absolute;top: -8px;right: -8px;color: #969799;font-size: 18px;background-color: #fff;border-radius: 100%;z-index: 100;
}
.van-uploader__mask {position: absolute;top: 0;right: 0;bottom: 0;left: 0;display: -webkit-box;display: -webkit-flex;display: flex;-webkit-box-orient: vertical;-webkit-box-direction: normal;-webkit-flex-direction: column;flex-direction: column;-webkit-box-align: center;-webkit-align-items: center;align-items: center;-webkit-box-pack: center;-webkit-justify-content: center;justify-content: center;color: #fff;background-color: rgba(50, 50, 51, 0.88);z-index: 1000;
}
</style>
<style>
input[type="file"] {outline: none;/*margin-top: 20px;*/
}#clip_button {position: absolute;right: 10%;bottom: 20px;width: 80px;height: 40px;border: none;border-radius: 2px;background: #1AAD19;color: #fff;
}#cancel_clip {position: absolute;left: 10%;bottom: 20px;width: 80px;height: 40px;border: none;border-radius: 2px;color: #fff;background: #E64340;
}#clip_container.container {z-index: 99999;position: fixed;padding-top: 60px;left: 0;top: 0;right: 0;bottom: 0;background: rgba(0, 0, 0, 1);
}#clip_container.container > div {position: absolute;width: 100%;height: 100%;top: 50%;left: 50%;-webkit-transform: translate(-50%, -50%);transform: translate(-50%, -50%);
}#clip_image {max-width: 100%;
}.cropper-container {font-size: 0;line-height: 0;position: relative;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;direction: ltr;-ms-touch-action: none;touch-action: none;
}.crop_loading,
.crop_success {display: none;position: fixed;top: 0;left: 0;width: 100%;height: 100%;z-index: 9;
}.crop_loading .crop_content {position: fixed;top: 50%;left: 50%;display: -webkit-box;display: -webkit-flex;display: flex;-webkit-box-orient: vertical;-webkit-box-direction: normal;-webkit-flex-direction: column;flex-direction: column;-webkit-box-align: center;-webkit-align-items: center;align-items: center;-webkit-box-pack: center;-webkit-justify-content: center;justify-content: center;box-sizing: content-box;width: 88px;max-width: 70%;min-height: 88px;padding: 10px;color: #fff;font-size: 14px;line-height: 20px;white-space: pre-wrap;text-align: center;word-wrap: break-word;background-color: rgba(0, 0, 0, 0.7);border-radius: 8px;-webkit-transform: translate3d(-50%, -50%, 0);transform: translate3d(-50%, -50%, 0);
}.crop_loading .crop_content img {margin-top: 15px;margin-bottom: 10px;
}.crop_success .crop_success_text {position: absolute;top: 50%;left: 50%;text-align: center;background: #000;opacity: 0.9;width: 120px;height: 30px;color: #fff;line-height: 30px;font-size: 16px;-webkit-border-radius: 3px;border-radius: 3px;-webkit-transform: translate(-50%, -50%);transform: translate(-50%, -50%);
}.cropper-container img {/* Avoid margin top issue (Occur only when margin-top <= -height) */display: block;min-width: 0 !important;max-width: none !important;min-height: 0 !important;max-height: none !important;width: 100%;height: 100%;image-orientation: 0deg;
}.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {position: absolute;top: 0;right: 0;bottom: 0;left: 0;
}.cropper-wrap-box {overflow: hidden;
}.cropper-drag-box {opacity: 0;background-color: #fff;
}.cropper-modal {opacity: 0.5;background-color: #000;
}.cropper-view-box {display: block;overflow: hidden;width: 100%;height: 100%;outline: 1px solid #39f;outline-color: rgba(51, 153, 255, 0.75);
}.cropper-dashed {position: absolute;display: block;opacity: 0.5;border: 0 dashed #eee;
}.cropper-dashed.dashed-h {top: 33.33333%;left: 0;width: 100%;height: 33.33333%;border-top-width: 1px;border-bottom-width: 1px;
}.cropper-dashed.dashed-v {top: 0;left: 33.33333%;width: 33.33333%;height: 100%;border-right-width: 1px;border-left-width: 1px;
}.cropper-center {position: absolute;top: 50%;left: 50%;display: block;width: 0;height: 0;opacity: 0.75;
}.cropper-center:before,
.cropper-center:after {position: absolute;display: block;content: " ";background-color: #eee;
}.cropper-center:before {top: 0;left: -3px;width: 7px;height: 1px;
}.cropper-center:after {top: -3px;left: 0;width: 1px;height: 7px;
}.cropper-face,
.cropper-line,
.cropper-point {position: absolute;display: block;width: 100%;height: 100%;opacity: 0.1;
}.cropper-face {top: 0;left: 0;background-color: #fff;
}.cropper-line {background-color: #39f;
}.cropper-line.line-e {top: 0;right: -3px;width: 5px;cursor: e-resize;
}.cropper-line.line-n {top: -3px;left: 0;height: 5px;cursor: n-resize;
}.cropper-line.line-w {top: 0;left: -3px;width: 5px;cursor: w-resize;
}.cropper-line.line-s {bottom: -3px;left: 0;height: 5px;cursor: s-resize;
}.cropper-point {width: 5px;height: 5px;opacity: 0.75;background-color: #39f;
}.cropper-point.point-e {top: 50%;right: -3px;margin-top: -3px;cursor: e-resize;
}.cropper-point.point-n {top: -3px;left: 50%;margin-left: -3px;cursor: n-resize;
}.cropper-point.point-w {top: 50%;left: -3px;margin-top: -3px;cursor: w-resize;
}.cropper-point.point-s {bottom: -3px;left: 50%;margin-left: -3px;cursor: s-resize;
}.cropper-point.point-ne {top: -3px;right: -3px;cursor: ne-resize;
}.cropper-point.point-nw {top: -3px;left: -3px;cursor: nw-resize;
}.cropper-point.point-sw {bottom: -3px;left: -3px;cursor: sw-resize;
}.cropper-point.point-se {right: -3px;bottom: -3px;width: 20px;height: 20px;cursor: se-resize;opacity: 1;
}@media (min-width: 768px) {.cropper-point.point-se {width: 15px;height: 15px;}
}@media (min-width: 992px) {.cropper-point.point-se {width: 10px;height: 10px;}
}@media (min-width: 1200px) {.cropper-point.point-se {width: 5px;height: 5px;opacity: 0.75;}
}.cropper-point.point-se:before {position: absolute;right: -50%;bottom: -50%;display: block;width: 200%;height: 200%;content: " ";opacity: 0;background-color: #39f;
}.cropper-invisible {opacity: 0;
}.cropper-bg {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMzTjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
}.cropper-hide {position: absolute;display: block;width: 0;height: 0;
}.cropper-hidden {display: none !important;
}.cropper-move {cursor: move;
}.cropper-crop {cursor: crosshair;
}.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {cursor: not-allowed;
}
</style>

父组件中运用:

import imageCrop from "./components/imageCrop.vue";

<van-fieldv-model="ruleForm.whitePhoneUrl"name="uploader"label:rules="rules.whitePhoneUrl"><template #input><!-- <van-uploaderv-model="uploader":max-count="1":before-read="beforeRead":after-read="afterRead":before-delete="delImgId"></van-uploader> --><!-- 自定义裁剪组件 --><image-crop:uploadLoading="uploadLoading":imgUrl="ruleForm.whitePhoneUrl"@before-upload="beforeRead"@after-upload="cropUpload"></image-crop><div class="notice"><div>1、请上传标准白底1寸证件照</div><div>2、该照片将应用于工卡制作、社保等</div></div></template></van-field>
beforeRead(file, callback) {const isImgType = file.type === "image/jpeg" || file.type === "image/png";const isLt10M = file.size / 1024 / 1024 < 10;if (!isImgType) {this.$toast("上传图片只能是 JPG或PNG 格式!");callback(false);return false;}if (!isLt10M) {this.$toast("上传图片大小不能超过 10MB!");callback(false);return false;}callback(true);return true;},
afterRead(file) {file.status = "uploading";file.message = "上传中...";request.uploadPic(file).then((res) => {if (res.code == 1) {file.status = "done";file.message = "上传完成";this.ruleForm.whitePhotoId = res.data;} else {file.status = "failed";file.message = "上传失败";this.$toast(res.data);}});},
// 裁剪点击确定cropUpload(file) {this.uploadLoading = true;request.uploadPic(file).then((res) => {if (res.code == 1) {this.uploadLoading = false;this.ruleForm.whitePhotoId = res.data;} else {this.uploadLoading = false;this.$toast(res.data);}});},
delImgId(file) {this.ruleForm.whitePhotoId = "";this.ruleForm.whitePhoneUrl = "";return true;},

 

这篇关于基于Vue的移动端图片裁剪组件 vue-crop的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/296581

相关文章

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

C#中图片如何自适应pictureBox大小

《C#中图片如何自适应pictureBox大小》文章描述了如何在C#中实现图片自适应pictureBox大小,并展示修改前后的效果,修改步骤包括两步,作者分享了个人经验,希望对大家有所帮助... 目录C#图片自适应pictureBox大小编程修改步骤总结C#图片自适应pictureBox大小上图中“z轴

使用Python将长图片分割为若干张小图片

《使用Python将长图片分割为若干张小图片》这篇文章主要为大家详细介绍了如何使用Python将长图片分割为若干张小图片,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. python需求的任务2. Python代码的实现3. 代码修改的位置4. 运行结果1. Python需求

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

基于Qt Qml实现时间轴组件

《基于QtQml实现时间轴组件》时间轴组件是现代用户界面中常见的元素,用于按时间顺序展示事件,本文主要为大家详细介绍了如何使用Qml实现一个简单的时间轴组件,需要的可以参考下... 目录写在前面效果图组件概述实现细节1. 组件结构2. 属性定义3. 数据模型4. 事件项的添加和排序5. 事件项的渲染如何使用

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用

使用 Python 和 LabelMe 实现图片验证码的自动标注功能

《使用Python和LabelMe实现图片验证码的自动标注功能》文章介绍了如何使用Python和LabelMe自动标注图片验证码,主要步骤包括图像预处理、OCR识别和生成标注文件,通过结合Pa... 目录使用 python 和 LabelMe 实现图片验证码的自动标注环境准备必备工具安装依赖实现自动标注核心