Vue上传图片+截屏

效果图:


 

代码

 html

<template>
  <div class="n-upload">
    <input type="file" style="display: none" @change="nUpload" ref="nUpload" />
    <n-clip
      v-show="clipConf.src"
      :clipW="200"
      :clipH="300"
      :src="clipConf.src"
      ref="nClip"
    >
    </n-clip>
    <div v-show="!clipConf.src" @click="uploadClick()" class="n-upload-btn">
      <i class="el-icon-plus"></i>
      <p>点击上传</p>
    </div>
    <el-button type="primary" @click="clipImg">确定</el-button>
    <img :src="img" />
  </div>
</template>
<script>
export default {
  data() {
    return {
      clipConf: {
        show: true,
        src: "",
      },
      img: "",
      activeName: 0, //判断图片上传,视频上传
    };
  },
  components: {
    "n-clip": () => import("@/accountnum/components/clip"),
  },
  watch: {
    "clipConf.src"(v) {
      if (!v) return;
      setTimeout(() => this.$refs.nClip.resize(), 300);
      this.formdata = v;
    },
  },
  methods: {
    clipImg() {
      let src;
      if (this.activeName == 0)
        src = this.clipConf.outSrc = this.$refs.nClip.out();
      else src = this.clipConf.outSrc = this.$refs.videoClip.out();
      console.log(src);
      this.img = src;
    },
    async nUpload({ target: tg }) {
      const file = tg.files[0];
      this.noneForm = file;
      tg.value = "";
      if (!file) return;
      const rd = new FileReader();
      rd.readAsDataURL(file);
      rd.onload = () => (this.clipConf.src = rd.result);
    },
    uploadClick() {
      this.$refs.nUpload.click();
    },
  },
};
</script>

 clip.vue

<style lang="scss" scoped>
	.n-clip {
		width: 100%;
		height: 300px;
	}
</style>

<template>
	<canvas v-resize="resize" @dragstart.prevent="" :style="{ cursor: hover ? 'move' : 'auto' }"
		@mousewheel.prevent.stop="onWheel" @DOMMouseScroll.prevent.stop="onWheel"
		@mousemove="(e) => img && (hover = isImgRange(...getCanvasXy(e)))" @mousedown="onDown" ref="c"
		class="n-clip"></canvas>
</template>

<script>
	const resizes = new Set();
	const resizeLoop = () => {
		for (const el of resizes) {
			if (el._oWidth === el.clientWidth && el._oHeight === el.clientHeight) continue;

			el._oWidth = el.clientWidth;
			el._oHeight = el.clientHeight;
			 el._resizeFunc(el)
		}
		window.requestAnimationFrame(resizeLoop)
	}
	window.requestAnimationFrame(resizeLoop);
	export default {
		props: {
			src: String,
			bg: {
				type: String,
				default: "#000",
			},
			scaleRatio: {
				// scale调整一次的比例
				type: Number,
				default: 0.05,
			},
			clipW: {
				type: Number,
				default: 100,
			},
			clipH: {
				type: Number,
				default: 100,
			},
		},
		watch: {
			clipW() {
				this.minSize();
			},
			clipH() {
				this.minSize();
			},

			src: {
				immediate: true,
				async handler(v1, v2) {
					console.log(5666)
					if (v1 === v2) return;
					if (!v1) return (this.img = null);
					const img = (this.img = new Image());
					img.src = v1;
					const res = await new Promise(
						(r) => ((img.onload = () => r(1)), (img.onerror = () => r(0)))
					);
					res && this.initImgParams();
				},
			},
			scale(v1, v2) {
				if (v1 === v2 || this.skipScaleCb) return (this.skipScaleCb = false);
				const {
					img,
					imgW,
					imgH,
					imgNW,
					imgNH
				} = this;
				if (v1 !== v1 || !img) return (this.scale = 1);

				this.minSize();

				this.imgW = imgNW * this.scale;
				this.imgH = imgNH * this.scale;

				this.boundary();
			},
		},
		data() {
			return {
				// 界面参数
				w: 100,
				h: 100,

				// 鼠标参数
				mouseX: 0,
				mouseY: 0,
				isMove: false,
				hover: false,

				// 图片参数
				imgX: 0,
				imgY: 0,
				imgW: 0,
				imgH: 0,
				imgNW: 0,
				imgNH: 0,
				skipScaleCb: false,
				scale: 1,

				c: null,
				ctx: null,
			};
		},
		directives: {
			'resize': {
				bind(el, binding, vNode) {
					el._oWidth = el.clientWidth;
					el._oHeight = el.clientHeight;
					el._resizeFunc = binding.value;
					el._resizeVNode = vNode;
					el._resizeId = new Date().getTime();
					resizes.add(el)
				},
				unbind(el) {
					resizes.delete(el);
				}


			}
		},
		mounted() {
			this.c = this.$refs.c;
			const ctx = (this.ctx = this.c.getContext("2d"));
			this.resize();
			this.loop();

			document.addEventListener("mousemove", this.onMove);
			document.addEventListener("mouseup", this.onUp);
		},
		beforeDestroy() {
			document.removeEventListener("mousemove", this.onMove);
			document.removeEventListener("mouseup", this.onUp);
		},
		methods: {
			resize() {
				// console.log(123);
				if (!this._isMounted || this._isDestroyed) return;
				const {
					c,
					clipW,
					clipH
				} = this;
				this.w = c.width = c.clientWidth;
				this.h = c.height = c.clientHeight;
				this.boundary();
			},
			// 初始化图片比例
			initImgParams() {
				const {
					img,
					w,
					h
				} = this;
				if (!img) return;
				const {
					naturalWidth: nw,
					naturalHeight: nh
				} = img;
				this.imgNW = nw;
				this.imgNH = nh;

				if (nw === 0 || nh === 0) return (this.scale = 1);
				const [r1, r2] = [(w / nw) * 0.8, (h / nh) * 0.8];
				this.scale = r1 < r2 ? r1 : r2;
				this.minSize();

				this.imgW = nw * this.scale;
				this.imgH = nh * this.scale;
				// 置于中间
				this.imgX = (w - this.imgW) / 2;
				this.imgY = (h - this.imgH) / 2;

				this.boundary();

				this.skipScaleCb = true;
			},
			minSize() {
				const {
					img,
					imgNW,
					imgNH,
					clipW,
					clipH,
					scale
				} = this;
				if (!img) return;
				// 计算最小值不超出裁剪框
				const [r1, r2] = [clipW / imgNW, clipH / imgNH];
				const min = r1 > r2 ? r1 : r2;

				return scale < min ? ((this.scale = min), true) : false;
			},
			// 边界
			boundary() {
				let {
					w,
					h,
					imgW,
					imgH,
					imgX,
					imgY,
					clipW,
					clipH
				} = this;
				let [clipX, clipY] = [(w - clipW) / 2, (h - clipH) / 2];

				if (imgX > clipX) imgX = clipX;
				if (imgX + imgW < clipX + clipW) imgX = clipX + clipW - imgW;
				if (imgY > clipY) imgY = clipY;
				if (imgY + imgH < clipY + clipH) imgY = clipY + clipH - imgH;

				this.imgX = imgX;
				this.imgY = imgY;
			},
			isImgRange(x, y) {
				let {
					w,
					h,
					imgW,
					imgH,
					imgX,
					imgY,
					clipW,
					clipH,
					img
				} = this;
				let [clipX, clipY] = [(w - clipW) / 2, (h - clipH) / 2];
				if (!img) return false;
				return x >= imgX && x <= imgX + imgW && y >= imgY && y <= imgY + imgH;
			},

			getCanvasXy(e) {
				let {
					clientX,
					clientY
				} = e.targetTouches ? e.targetTouches[0] : e;
				const {
					x: boxX,
					y: boxY
				} = this.c.getBoundingClientRect();
				return [clientX - boxX, clientY - boxY];
			},
			onDown(e) {
				if (!this.img) return;
				const [x, y] = this.getCanvasXy(e);
				if (!this.isImgRange(x, y)) return;
				this.mouseX = x;
				this.mouseY = y;
				this.isMove = true;
			},
			onMove(e) {
				const {
					mouseX,
					mouseY,
					isMove
				} = this;
				if (!isMove) return;
				const [x, y] = this.getCanvasXy(e);

				this.mouseX = x;
				this.mouseY = y;
				this.imgX += x - mouseX;
				this.imgY += y - mouseY;

				this.boundary();
			},
			onUp(e) {
				this.isMove = false;
			},
			onWheel({
				wheelDelta,
				detail
			}) {
				const {
					img,
					imgW,
					imgH,
					imgNW,
					imgNH
				} = this;
				if (!img) return;

				this.scale +=
					((wheelDelta || -detail) > 0 ? 1 : -1) * (this.scale * this.scaleRatio);
				this.minSize();
				this.imgW = imgNW * this.scale;
				this.imgH = imgNH * this.scale;
				// 变换原点
				this.imgX += (imgW - this.imgW) / 2;
				this.imgY += (imgH - this.imgH) / 2;
				this.boundary();

				this.skipScaleCb = true;
			},
			// 输出裁剪后的图片
			out(q = 1) {
				const {
					ctx,
					w,
					h,
					clipW,
					clipH
				} = this;
				const c2 = document.createElement("canvas");
				const ctx2 = c2.getContext("2d");
				c2.width = clipW;
				c2.height = clipH;
				ctx2.putImageData(
					ctx.getImageData((w - clipW) / 2, (h - clipH) / 2, clipW, clipH),
					0,
					0
				);
				return c2.toDataURL("image/png", q);
			},
			draw() {
				let {
					ctx,
					w,
					h,
					img,
					clipW,
					clipH,
					bg,
					imgX,
					imgY,
					imgW,
					imgH
				} = this;
				// 背景
				ctx.beginPath();
				ctx.clearRect(0, 0, w, h);
				ctx.rect(0, 0, w, h);
				if (bg)(ctx.fillStyle = bg), ctx.fill();

				// 图片
				if (!img) return;

				ctx.save();
				ctx.translate(imgX + imgW * 0.5, imgY + imgH * 0.5);
				ctx.drawImage(img, imgW * -0.5, imgH * -0.5, imgW, imgH);
				ctx.restore();

				ctx.fillStyle = "rgba(0,0,0,0.4)";
				ctx.fill();

				// 裁剪框
				const [clipX, clipY] = [(w - clipW) / 2, (h - clipH) / 2];
				ctx.beginPath();
				ctx.rect(clipX, clipY, clipW, clipH);

				ctx.save();
				ctx.clip();
				ctx.translate(imgX + imgW * 0.5, imgY + imgH * 0.5);
				ctx.drawImage(img, imgW * -0.5, imgH * -0.5, imgW, imgH);
				ctx.restore();

				ctx.beginPath();
				ctx.lineWidth = 1.5;
				ctx.strokeStyle = "#3da7b4";
				ctx.strokeRect(clipX - 1.5, clipY - 1.5, clipW + 3, clipH + 3);
			},
			loop() {
				if (this._isDestroyed) return;
				this.draw();
				window.requestAnimationFrame(this.loop.bind(this));
			},
		},
	};
</script>

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue是一种流行的JavaScript框架,用于构建用户界面。KindEditor是一个富文本编辑器,可以方便地在网页中编辑和展示富文本内容。图片上传是指将用户选择的图片文件上传到服务器。 在Vue中使用KindEditor进行图片上传的步骤如下: 1. 安装KindEditor:可以通过npm或者直接下载KindEditor的源代码进行安装。 2. 在Vue组件中引入KindEditor:可以通过import语句引入KindEditor的相关文件。 3. 在Vue组件中使用KindEditor:在需要使用富文本编辑器的地方,使用KindEditor提供的API进行初始化和配置。 4. 实现图片上传功能:通过配置KindEditor的图片上传接口,将用户选择的图片文件上传到服务器。 以下是一个简单的示例代码: ``` <template> <div> <textarea ref="editor"></textarea> <button @click="uploadImage">上传图片</button> </div> </template> <script> import KindEditor from 'kindeditor' export default { mounted() { KindEditor.create(this.$refs.editor, { uploadJson: '/uploadImage', // 图片上传接 allowFileManager: false // 是否允许文件管理 }) }, methods: { uploadImage() { // 获取编辑器中的图片文件 const images = KindEditor.editor(this.$refs.editor).images // 将图片文件上传到服务器 // ... } } } </script> ``` 相关问题: 1. Vue是什么? 2. KindEditor是什么? 3. 如何在Vue中使用KindEditor? 4. 如何实现图片上传功能? 5. 还有其他类似的富文本编辑器可以使用吗?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值