1. <template>
  2. <div class="clip-img" :style="imgStyle">
  3. <img :src="url" alt="" crossOrigin="anonymous" :style="imgStyle">
  4. <canvas ref="canvas" :style="imgStyle" @mousedown="onmousedown" @mousemove="onmousemove"></canvas>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name:"clip-image",
  10. props:{
  11. max:{ // 缩放基准宽度或高度
  12. type:Number,
  13. default:400
  14. }
  15. },
  16. data(){
  17. return {
  18. img:null,
  19. url:null,
  20. imgInfo: null, // 压缩前的信息
  21. imgCInfo:null, // 压缩后的信息
  22. clipInfo:null, // 压缩前的信息
  23. clipCinfo:null, // 压缩后的信息
  24. ctx:null, // 画板
  25. pos:{x:0,y:0},
  26. lock: "", // 锁住一个方向
  27. boundary:null
  28. }
  29. },
  30. computed:{
  31. imgStyle(){
  32. let imgCInfo = this.imgCInfo;
  33. if(imgCInfo){
  34. return {
  35. width: `${imgCInfo.w}px`,
  36. height: `${imgCInfo.h}px`
  37. }
  38. }
  39. return {height:"0px",height:"0px",display:"none"};
  40. }
  41. },
  42. async mounted(){
  43. this.clear();
  44. this.ctx = this.$refs.canvas.getContext("2d");
  45. window.addEventListener("mouseup",this.onmouseup);
  46. },
  47. methods:{
  48. init(src,bound){
  49. this.setSrc(src)
  50. this.setClip(bound);
  51. },
  52. // 外部调用
  53. async setSrc(src){
  54. let img = await this.loadImage(src,{crossOrigin:"anonymous"});
  55. this.img = img;
  56. this.setImg(img);
  57. },
  58. clear(){
  59. let obj = {
  60. img:null,
  61. url:null,
  62. imgInfo: null, // 压缩前的信息
  63. imgCInfo:null, // 压缩后的信息
  64. clipInfo:null, // 压缩前的信息
  65. clipCinfo:null, // 压缩后的信息
  66. ctx:null, // 画板
  67. pos:{x:0,y:0},
  68. lock: "", // 锁住一个方向
  69. boundary:null
  70. }
  71. for(let i in obj){
  72. this[i] = obj[i];
  73. }
  74. },
  75. loadImage(url,attrs){
  76. this.url = url;
  77. let img = new Image();
  78. img.src = url;
  79. attrs = attrs || {};
  80. for(let i in attrs){
  81. img[i] = attrs[i];
  82. }
  83. return new Promise((resolve,reject)=>{
  84. img.onload = function(){
  85. resolve(img);
  86. };
  87. img.onerror = reject;
  88. });
  89. },
  90. setImg(img){
  91. this.img = img;
  92. this.imgInfo = {
  93. w:img.width,
  94. h:img.height
  95. };
  96.  
  97. // 压缩图的比例
  98. let w,h,scale;
  99. if(img.width > img.height){
  100. w = this.max;
  101. scale = w/img.width;
  102. h = scale*img.height;
  103. }else{
  104. h = this.max;
  105. scale = (h/img.height);
  106. w = scale * img.width;
  107. }
  108. this.imgCInfo = {
  109. w,
  110. h,
  111. scale
  112. };
  113. let canvas = this.$refs.canvas;
  114. canvas.width = w;
  115. canvas.height = h;
  116. this.setClip();
  117. },
  118. setClip(clipInfo){
  119. if(clipInfo){
  120. this.clipInfo = {
  121. w:clipInfo.width,
  122. h:clipInfo.height
  123. };
  124. }
  125. if(this.imgCInfo && this.clipInfo){
  126. this.compressClip(this.imgCInfo,this.clipInfo);
  127. this.fill();
  128. }
  129. },
  130. compressClip(imgCInfo,clipInfo){
  131. // 压缩缩放
  132. let w,h,scale;
  133. let imgR = imgCInfo.w/imgCInfo.h;
  134. let clipR = clipInfo.w / clipInfo.h;
  135.  
  136. if(imgR > clipR){
  137. // 图片的宽度偏大
  138. h = imgCInfo.h;
  139. scale = h/clipInfo.h;
  140. w = scale * clipInfo.w;
  141. this.lock = "h";
  142. }else{
  143. // 图片的宽度偏小
  144. w = imgCInfo.w;
  145. scale = w/clipInfo.w;
  146. h = scale * clipInfo.h;
  147. this.lock = "w";
  148. }
  149. this.clipCinfo = {
  150. w,
  151. h,
  152. scale
  153. }
  154. this.boundary = {
  155. w:this.imgCInfo.w - this.clipCinfo.w,
  156. h:this.imgCInfo.h - this.clipCinfo.h
  157. };
  158. this.pos = {
  159. x:0,
  160. y:0
  161. };
  162. },
  163. onmouseup(){
  164. this.mouse = null;
  165. },
  166. onmousedown(e){
  167. this.mouse = {
  168. x:e.offsetX,
  169. y:e.offsetY
  170. }
  171. },
  172. onmousemove(e){
  173. if(this.mouse){
  174. let x = e.offsetX - this.mouse.x ;
  175. let y = e.offsetY - this.mouse.y;
  176. if(this.lock == "h"){
  177. x = this.pos.x + x;
  178. if(x < 0){
  179. x = 0;
  180. }else if(x > this.boundary.w){
  181. x = this.boundary.w
  182. }
  183. this.pos.x = x;
  184. }else{
  185. y = this.pos.y + y;
  186. if(y < 0){
  187. y = 0;
  188. }else if(y > this.boundary.h){
  189. y = this.boundary.h
  190. }
  191. this.pos.y = y;
  192. }
  193. this.mouse = {
  194. x:e.offsetX,
  195. y:e.offsetY
  196. }
  197. this.fill();
  198. }
  199. },
  200. fill(){
  201. let {w,h} = this.clipCinfo;
  202. let {x,y} = this.pos;
  203. let clipctx = this.ctx;
  204. let imgCInfo = this.imgCInfo;
  205. clipctx.clearRect(0, 0, imgCInfo.w, imgCInfo.h);
  206. clipctx.beginPath();
  207. clipctx.fillStyle = 'rgba(0,0,0,0.6)';
  208. clipctx.strokeStyle = "green";
  209. //遮罩层
  210. clipctx.globalCompositeOperation = "source-over";
  211. clipctx.fillRect(0, 0, imgCInfo.w, imgCInfo.h);
  212. //画框
  213. clipctx.globalCompositeOperation = 'destination-out';
  214. clipctx.fillRect(x, y, w, h);
  215. //描边
  216. clipctx.globalCompositeOperation = "source-over";
  217. clipctx.moveTo(x, y);
  218. clipctx.lineTo(x + w, y);
  219. clipctx.lineTo(x + w, y + h);
  220. clipctx.lineTo(x, y + h);
  221. clipctx.lineTo(x, y);
  222. clipctx.stroke();
  223. clipctx.closePath();
  224. },
  225. exportBase(){
  226. // 导出图片 base64
  227. let pos = this.pos;
  228. let scale = this.imgCInfo.scale;
  229. let sx = pos.x / scale;
  230. let sy = pos.y / scale;
  231. let swidth = parseInt(this.clipCinfo.w / scale);
  232. let sheight = parseInt(this.clipCinfo.h / scale);
  233. let canvas = document.createElement("canvas");
  234. canvas.width = swidth;
  235. canvas.height = sheight;
  236. let ctx = canvas.getContext("2d");
  237. ctx.drawImage(this.img,sx,sy,this.imgInfo.w,this.imgInfo.h,0,0,this.imgInfo.w,this.imgInfo.h);
  238. return canvas.toDataURL("image/png");
  239. },
  240. dataURLtoFile(b64Data,filename){
  241. filename = filename || "test.png";
  242. let mime = "image/png";
  243. var bstr = atob(b64Data.replace(/^data:image\/(png|jpeg|jpg);base64,/, ''));
  244. var n = bstr.length;
  245. var u8arr = new Uint8Array(n);
  246. while(n--){
  247. u8arr[n] = bstr.charCodeAt(n);
  248. }
  249. // 转换成file对象
  250. return new File([u8arr], filename, {type:mime});
  251.  
  252. // 转换成成blob对象
  253. // return new Blob([u8arr],{type:mime});
  254. // return blob;
  255. }
  256. },
  257. destroyed(){
  258. window.removeEventListener("mouseup",this.onmouseup);
  259. }
  260. }
  261. </script>
  262. <style lang="scss" scoped>
  263. .clip-img{
  264. border: 1px solid red;
  265. margin: 20px auto;
  266. position: relative;
  267. height: 0;
  268. width: 0;
  269. overflow: hidden;
  270. canvas{
  271. position: absolute;
  272. left: 0;
  273. top: 0;
  274. z-index: 1;
  275. cursor: move;
  276. }
  277. img{
  278. position: relative;
  279. z-index: 0;
  280. }
  281. }
  282. </style>

  

使用:

  1. <template>
  2. <div class="clip-img">
  3. <clipImage ref="clipImage"></clipImage>
  4. <button @click="getImg">导出</button> <button @click="slide">切换</button>
  5. <img :src="src" v-if="src" alt="" class="result">
  6. </div>
  7. </template>
  8. <script>
  9. import clipImage from "@/components/clip-image.vue";
  10. export default {
  11. name:"clip-image",
  12. data(){
  13. return {
  14. src:""
  15. }
  16. },
  17. components:{
  18. clipImage
  19. },
  20. mounted(){
  21. // setSrc
  22. this.$refs.clipImage.init("xxx",{width:400,height:200});
  23. },
  24. methods:{
  25. getImg(){
  26. this.src = this.$refs.clipImage.exportBase();
  27. console.log("截图成功")
  28. },
  29. slide(){
  30. this.$refs.clipImage.setSrc("xxx");
  31. }
  32. }
  33. }
  34. </script>
  35. <style lang="scss" scoped>
  36. .clip-img{
  37. .result{
  38. max-width: 400px;
  39. }
  40. }
  41. </style>

  

撸一个 vue 的截图组件,按比例截取的更多相关文章

  1. 手把手从零开始---封装一个vue视频播放器组件

    现在,在网页上播放视频已经越来越流行,但是网上的资料鱼龙混杂,很难找到自己想要的,今天小编就自己的亲身开发体验,手把手从零开始---封装一个vue视频播放器组件. 作为一个老道的前端搬砖师,怎么可能会 ...

  2. 纯手工撸一个vue框架

    前言 vue create 真的很方便,但是很多人欠缺的是手动撸一遍.有些人离开脚手架都不会开发了. Vue最简单的结构 步骤 搭建最基本的结构 打开空文件夹,通过 npm init 命令生成pack ...

  3. 从零开始徒手撸一个vue的toast弹窗组件

    相信普通的vue组件大家都会写,定义 -> 引入 -> 注册 -> 使用,行云流水,一气呵成,但是如果我们今天是要自定义一个弹窗组件呢? 首先,我们来分析一下弹窗组件的特性(需求): ...

  4. VUE -- 如何快速的写出一个Vue的icon组件?

    伴随着Vue的诞生,它似乎就被人寄予厚望,不仅仅是因为其轻量级的MVVM设计方式,而且其实现了组件化开发模式,所以越来越多的人会拿Vue和AngularJS.React Native做比较.具体关于它 ...

  5. 基于iview 封装一个vue 表格分页组件

    iview 是一个支持中大型项目的后台管理系统ui组件库,相对于一个后台管理系统的表格来说分页十分常见的 iview是一个基于vue的ui组件库,其中的iview-admin是一个已经为我们搭好的后天 ...

  6. 手把手教你实现一个 Vue 进度条组件!

    最近在个人的项目中,想对页面之间跳转的过程进行优化,想到了很多文档或 npm 等都用到的页面跳转进度条,于是便想自己去实现一个,特此记录. 来看下 npm 搜索组件时候的效果: so 下面咱们一起动手 ...

  7. 撸一个vue的双向绑定

    1.前言 说起双向绑定可能大家都会说:Vue内部通过Object.defineProperty方法属性拦截的方式,把data对象里每个数据的读写转化成getter/setter,当数据变化时通知视图更 ...

  8. 一个vue的日历组件

    说明: 1.基于element-ui开发的vue日历组件. 地址 更新: 1.增加value-format指定返回值的格式2.增加头部插槽自定义头部 <ele-calendar > < ...

  9. 来吧,自己动手撸一个分布式ID生成器组件

    在经过了众多轮的面试之后,小林终于进入到了一家互联网公司的基础架构组,小林目前在公司有使用到架构组研究到分布式id生成器,前一阵子大概看了下其内部的实现,发现还是存在一些架构设计不合理之处.但是又由于 ...

随机推荐

  1. Java对象为啥要实现Serializable接口

    Serializable接口概述 Serializable是java.io包中定义的.用于实现Java类的序列化操作而提供的一个语义级别的接口.Serializable序列化接口没有任何方法或者字段, ...

  2. Linux永久挂载新的硬盘

    1. 查看设备挂载的情况 fdisk -l 2. 查看数据盘是否格式化 lsblk -f 3. 如果没有,格式化硬盘 sudo mkfs.xfs /dev/vdb 4. 创建挂载点,例如 mkdir ...

  3. shell脚本将gbk文件转化为utf-8

    使用注意项: 原来文件格式gbk的,否则可能出现utf-8转utf-8乱码. #!/bin/bash function gbk2utf(){ file="$1" echo &quo ...

  4. [转]linux 下 使用 c / c++ 调用curl库 做通信开发

    example:   1. http://curl.haxx.se/libcurl/c/example.html  2. http://www.libcurl.org/book:  1. http:/ ...

  5. openresty开发系列1--网关API架构及选型

    微服务架构在项目中的应用越来越多,我们知道在微服务架构风格中,一个大应用被拆分成为了多个小的服务系统提供出来,这些小的系统他们可以自成体系,也就是说这些小系统可以拥有自己的数据库,框架甚至语言等,这些 ...

  6. Spark ML 中 VectorIndexer, StringIndexer等用法(转载)

    VectorIndexer 主要作用:提高决策树或随机森林等ML方法的分类效果.VectorIndexer是对数据集特征向量中的类别(离散值)特征(index categorical features ...

  7. HBase在特征工程中的应用

    前言HBase是一款分布式的NoSQL DB,可以轻松扩展存储和读写能力. 主要特性有: 按某精确的key获取对应的value(Get) 通过前缀匹配一段相邻的数据(Scan) 多版本 动态列 服务端 ...

  8. Microsoft VBScript 运行时错误 错误 800a005e 无效使用 Null: Replace

    查看数据库   表的字段里面是否有空的字段. where 字段名 is null

  9. Oracle中RAISE异常

    转: Oracle中RAISE异常 由三种方式抛出异常 1. 通过PL/SQL运行时引擎 2. 使用RAISE语句 3. 调用RAISE_APPLICATION_ERROR存储过程 当数据库或PL/S ...

  10. iOS 控制输入框的字数?(textFliedView,textFlied等)

    //控制输入框的字数 - (void)textViewDidChange:(UITextView *)textView { NSInteger number = [textView.text leng ...