index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import { defineComponent } from 'vue';
  2. import 'vue-cropper/dist/index.css';
  3. import { VueCropper } from 'vue-cropper';
  4. import styles from './index.module.less';
  5. export default defineComponent({
  6. name: 'col-cropper',
  7. components: { VueCropper },
  8. props: {
  9. hideInput: {
  10. type: Boolean,
  11. default: false
  12. },
  13. option: {
  14. type: Object
  15. },
  16. onCancelTailor: {
  17. type: Function,
  18. default: () => {}
  19. }, // 取消
  20. getBase64Data: {
  21. type: Function,
  22. default: () => {}
  23. },
  24. getBlob: {
  25. type: Function,
  26. default: () => {}
  27. },
  28. getFile: {
  29. type: Function,
  30. default: () => {}
  31. },
  32. imgOriginF: {
  33. type: Function,
  34. default: () => {}
  35. }
  36. },
  37. data() {
  38. return {
  39. img: '',
  40. config: {
  41. ceilbutton: false, //顶部按钮,默认底部
  42. outputSize: 1, //裁剪生成图片的质量
  43. outputType: 'png', //裁剪生成图片的格式,默认png
  44. info: false, //裁剪框的大小信息
  45. canScale: true, //图片是否允许滚轮缩放
  46. autoCrop: false, //是否默认生成截图框
  47. autoCropWidth: 0, //默认生成截图框宽度
  48. autoCropHeight: 0, //默认生成截图框高度
  49. fixed: true, //是否开启截图框宽高固定比例
  50. fixedNumber: [1, 1], //截图框的宽高比例
  51. full: false, //是否输出原图比例的截图
  52. fixedBox: true, //固定截图框大小 不允许改变
  53. canMove: true, //上传图片是否可以移动
  54. canMoveBox: false, //截图框能否拖动
  55. original: false, //上传图片按照原始比例渲染
  56. centerBox: true, //截图框是否被限制在图片里面
  57. high: true, //是否按照设备的dpr 输出等比例图片
  58. infoTrue: false, //true 为展示真实输出图片宽高 false 展示看到的截图框宽高
  59. maxImgSize: 2000, //限制图片最大宽度和高度
  60. enlarge: 1, //图片根据截图框输出比例倍数
  61. mode: '100%', //图片默认渲染方式
  62. cancelButtonText: '取消', //取消按钮文本
  63. confirmButtonText: '确定', //确定按钮文本
  64. cancelButtonBackgroundColor: '#606266', //取消按钮背景色
  65. confirmButtonBackgroundColor: '#ed594c', //确定按钮背景色
  66. cancelButtonTextColor: '#ffffff', //取消按钮字体色
  67. confirmButtonTextColor: '#ffffff' //确定按钮字体色
  68. }
  69. };
  70. },
  71. mounted() {
  72. this.config = Object.assign(this.config, this.option);
  73. },
  74. methods: {
  75. //添加网格线
  76. addSlide() {
  77. if (document.getElementById('vertical') == null) {
  78. let box = document.getElementsByClassName('cropper-crop-box')[0];
  79. //左网格线
  80. let verticalLeft = document.createElement('div');
  81. verticalLeft.id = 'vertical';
  82. verticalLeft.style.width = '1px';
  83. verticalLeft.style.height = '100%';
  84. verticalLeft.style.top = '0px';
  85. verticalLeft.style.left = '33%';
  86. verticalLeft.style.position = 'absolute';
  87. verticalLeft.style.backgroundColor = '#fff';
  88. verticalLeft.style.zIndex = '522';
  89. verticalLeft.style.opacity = '0.5';
  90. //右网格线
  91. let verticalRight = document.createElement('div');
  92. verticalRight.style.width = '1px';
  93. verticalRight.style.height = '100%';
  94. verticalRight.style.top = '0px';
  95. verticalRight.style.right = '33%';
  96. verticalRight.style.position = 'absolute';
  97. verticalRight.style.backgroundColor = '#fff';
  98. verticalRight.style.zIndex = '522';
  99. verticalRight.style.opacity = '0.5';
  100. //上网格线
  101. let verticalTop = document.createElement('div');
  102. verticalTop.style.width = '100%';
  103. verticalTop.style.height = '1px';
  104. verticalTop.style.top = '33%';
  105. verticalTop.style.left = '0px';
  106. verticalTop.style.position = 'absolute';
  107. verticalTop.style.backgroundColor = '#fff';
  108. verticalTop.style.zIndex = '522';
  109. verticalTop.style.opacity = '0.5';
  110. //下网格线
  111. let verticalBottom = document.createElement('div');
  112. verticalBottom.style.width = '100%';
  113. verticalBottom.style.height = '1px';
  114. verticalBottom.style.bottom = '33%';
  115. verticalBottom.style.left = '0px';
  116. verticalBottom.style.position = 'absolute';
  117. verticalBottom.style.backgroundColor = '#fff';
  118. verticalBottom.style.zIndex = '522';
  119. verticalBottom.style.opacity = '0.5';
  120. //左上边线
  121. let LeftTopSide = document.createElement('div');
  122. LeftTopSide.style.width = '30px';
  123. LeftTopSide.style.height = '4px';
  124. LeftTopSide.style.top = '-4px';
  125. LeftTopSide.style.left = '-4px';
  126. LeftTopSide.style.position = 'absolute';
  127. LeftTopSide.style.backgroundColor = '#fff';
  128. LeftTopSide.style.zIndex = '522';
  129. LeftTopSide.style.opacity = '1';
  130. //上左边线
  131. let TopListSide = document.createElement('div');
  132. TopListSide.style.width = '4px';
  133. TopListSide.style.height = '30px';
  134. TopListSide.style.top = '-4px';
  135. TopListSide.style.left = '-4px';
  136. TopListSide.style.position = 'absolute';
  137. TopListSide.style.backgroundColor = '#fff';
  138. TopListSide.style.zIndex = '522';
  139. TopListSide.style.opacity = '1';
  140. //右上边线
  141. let RightTopSide = document.createElement('div');
  142. RightTopSide.style.width = '30px';
  143. RightTopSide.style.height = '4px';
  144. RightTopSide.style.top = '-4px';
  145. RightTopSide.style.right = '-4px';
  146. RightTopSide.style.position = 'absolute';
  147. RightTopSide.style.backgroundColor = '#fff';
  148. RightTopSide.style.zIndex = '522';
  149. RightTopSide.style.opacity = '1';
  150. //上右边线
  151. let TopRightSide = document.createElement('div');
  152. TopRightSide.style.width = '4px';
  153. TopRightSide.style.height = '30px';
  154. TopRightSide.style.top = '-4px';
  155. TopRightSide.style.right = '-4px';
  156. TopRightSide.style.position = 'absolute';
  157. TopRightSide.style.backgroundColor = '#fff';
  158. TopRightSide.style.zIndex = '522';
  159. TopRightSide.style.opacity = '1';
  160. //左下边线
  161. let LeftBottomSide = document.createElement('div');
  162. LeftBottomSide.style.width = '30px';
  163. LeftBottomSide.style.height = '4px';
  164. LeftBottomSide.style.bottom = '-4px';
  165. LeftBottomSide.style.left = '-4px';
  166. LeftBottomSide.style.position = 'absolute';
  167. LeftBottomSide.style.backgroundColor = '#fff';
  168. LeftBottomSide.style.zIndex = '522';
  169. LeftBottomSide.style.opacity = '1';
  170. //下左边线
  171. let BottomListSide = document.createElement('div');
  172. BottomListSide.style.width = '4px';
  173. BottomListSide.style.height = '30px';
  174. BottomListSide.style.bottom = '-4px';
  175. BottomListSide.style.left = '-4px';
  176. BottomListSide.style.position = 'absolute';
  177. BottomListSide.style.backgroundColor = '#fff';
  178. BottomListSide.style.zIndex = '522';
  179. BottomListSide.style.opacity = '1';
  180. //右下边线
  181. let RightBottomSide = document.createElement('div');
  182. RightBottomSide.style.width = '30px';
  183. RightBottomSide.style.height = '4px';
  184. RightBottomSide.style.bottom = '-4px';
  185. RightBottomSide.style.right = '-4px';
  186. RightBottomSide.style.position = 'absolute';
  187. RightBottomSide.style.backgroundColor = '#fff';
  188. RightBottomSide.style.zIndex = '522';
  189. RightBottomSide.style.opacity = '1';
  190. //下右边线
  191. let BottomRightSide = document.createElement('div');
  192. BottomRightSide.style.width = '4px';
  193. BottomRightSide.style.height = '30px';
  194. BottomRightSide.style.bottom = '-4px';
  195. BottomRightSide.style.right = '-4px';
  196. BottomRightSide.style.position = 'absolute';
  197. BottomRightSide.style.backgroundColor = '#fff';
  198. BottomRightSide.style.zIndex = '522';
  199. BottomRightSide.style.opacity = '1';
  200. //一起生成
  201. box.appendChild(verticalLeft);
  202. box.appendChild(verticalRight);
  203. box.appendChild(verticalTop);
  204. box.appendChild(verticalBottom);
  205. box.appendChild(LeftTopSide);
  206. box.appendChild(TopListSide);
  207. box.appendChild(RightTopSide);
  208. box.appendChild(TopRightSide);
  209. box.appendChild(LeftBottomSide);
  210. box.appendChild(BottomListSide);
  211. box.appendChild(RightBottomSide);
  212. box.appendChild(BottomRightSide);
  213. }
  214. },
  215. //异步onload图片
  216. onLoadImg(photoUrl: any) {
  217. return new Promise(function (resolve, reject) {
  218. let reader = new FileReader();
  219. reader.readAsDataURL(photoUrl);
  220. reader.onload = (e: any) => {
  221. resolve(e.target['result']);
  222. };
  223. });
  224. },
  225. /**
  226. * 载入文件
  227. * template:
  228. * <h5-cropper hide-input ref="cropper">
  229. *
  230. * javascript:
  231. * this.$refs.cropper.loadFile()
  232. *
  233. * @param file
  234. */
  235. loadFile(file: any) {
  236. if (file instanceof File) {
  237. this.onLoadImg(file).then((base64: any) => {
  238. this.img = base64;
  239. setTimeout(() => {
  240. this.config.autoCrop = true;
  241. this.addSlide();
  242. }, 10);
  243. });
  244. } else {
  245. throw new Error('Arguments file is not File');
  246. }
  247. },
  248. /**
  249. *
  250. * @param base64
  251. */
  252. loadBase64(base64: string) {
  253. if (typeof base64 !== 'string') {
  254. throw new Error('Arguments base64 is not string');
  255. }
  256. const base = base64.split(',');
  257. if (!/^data:image\/(.*?);base64$/.test(base[0])) {
  258. throw new Error('Arguments base64 MIME is not image/*');
  259. }
  260. // Base64 Regex @see https://learnku.com/articles/42295
  261. if (
  262. !/^[\/]?([\da-zA-Z]+[\/+]+)*[\da-zA-Z]+([+=]{1,2}|[\/])?$/.test(base[1])
  263. ) {
  264. throw new Error('Not standard base64');
  265. }
  266. this.img = base64;
  267. setTimeout(() => {
  268. this.config.autoCrop = true;
  269. this.addSlide();
  270. }, 10);
  271. },
  272. rotating(e: any) {
  273. (this as any).$refs.cropper.rotateRight();
  274. // document.getElementsByClassName("cropper-modal")[0].style = "background-color: rgba(0,0,0,0.5);transition: 0.88s";
  275. },
  276. canceltailor() {
  277. this.img = '';
  278. this.onCancelTailor();
  279. },
  280. tailoring() {
  281. // 获取截图的base64数据
  282. (this as any).$refs.cropper.getCropData((data: any) => {
  283. this.getBase64Data(data);
  284. this.getBlob(data);
  285. this.img = '';
  286. this.config.autoCrop = false;
  287. });
  288. // 获取截图的blob数据
  289. (this as any).$refs.cropper.getCropBlob((data: BlobPart) => {
  290. this.getBase64Data(data);
  291. this.getBlob(data);
  292. // Blob 转 File
  293. const suffix = {
  294. jpeg: 'jpg',
  295. png: 'png',
  296. webp: 'webp'
  297. }[this.config.outputType];
  298. const time = new Date().getTime();
  299. const file = new File([data], `${time}.${suffix}`, {
  300. type: `image/${this.config.outputType}`
  301. });
  302. this.getFile(file);
  303. this.img = '';
  304. this.config.autoCrop = false;
  305. });
  306. },
  307. async upPhoto(e: any) {
  308. let photoUrl = e.target.files[0];
  309. (this as any).$refs.headInput.value = null;
  310. if (photoUrl != undefined) {
  311. this.imgOriginF(photoUrl);
  312. this.img = (await this.onLoadImg(photoUrl)) as string;
  313. this.config.autoCrop = true;
  314. setTimeout(() => {
  315. this.addSlide();
  316. }, 20);
  317. }
  318. },
  319. onCropMoving(e: any) {
  320. // console.log('onCropMoving')
  321. },
  322. onImgMoving(e: any) {
  323. // console.log('onCropMoving')
  324. }
  325. },
  326. render() {
  327. return (
  328. <div class={[styles.upbtn, styles.uploadWarper]}>
  329. {this.hideInput}
  330. {!this.hideInput ? (
  331. <input
  332. style="opacity: 0;"
  333. class={styles.upbtn}
  334. type="file"
  335. accept="image/*"
  336. onChange={this.upPhoto}
  337. ref="headInput"
  338. />
  339. ) : null}
  340. {this.img != '' ? (
  341. <div class={styles.bg}>
  342. {this.config.ceilbutton ? (
  343. <div class={styles.btndiv}>
  344. <div
  345. class={styles.btn}
  346. onClick={this.canceltailor}
  347. style={{
  348. backgroundColor: this.config.cancelButtonBackgroundColor,
  349. color: this.config.cancelButtonTextColor
  350. }}>
  351. {this.config.cancelButtonText}
  352. </div>
  353. <div class={styles.img} onClick={this.rotating}></div>
  354. <div
  355. class={styles.btn}
  356. onClick={this.tailoring}
  357. style={{
  358. backgroundColor: this.config.confirmButtonBackgroundColor,
  359. color: this.config.confirmButtonTextColor
  360. }}>
  361. {this.config.confirmButtonText}
  362. </div>
  363. </div>
  364. ) : null}
  365. <div class={styles.wrapper}>
  366. <VueCropper
  367. ref="cropper"
  368. img={this.img}
  369. outputSize={this.config.outputSize}
  370. outputType={this.config.outputType}
  371. info={this.config.info}
  372. canScale={this.config.canScale}
  373. autoCrop={this.config.autoCrop}
  374. autoCropWidth={this.config.autoCropWidth}
  375. autoCropHeight={this.config.autoCropHeight}
  376. fixedBox={this.config.fixedBox}
  377. fixed={this.config.fixed}
  378. fixedNumber={this.config.fixedNumber}
  379. full={this.config.full}
  380. canMove={this.config.canMove}
  381. canMoveBox={this.config.canMoveBox}
  382. original={this.config.original}
  383. centerBox={this.config.centerBox}
  384. high={this.config.high}
  385. infoTrue={this.config.infoTrue}
  386. maxImgSize={this.config.maxImgSize}
  387. enlarge={this.config.enlarge}
  388. mode={this.config.mode}
  389. onCropMoving={this.onCropMoving}
  390. onImgMoving={this.onImgMoving}></VueCropper>
  391. </div>
  392. {!this.config.ceilbutton ? (
  393. <div class={styles.btndiv}>
  394. <div
  395. class={styles.btn}
  396. onClick={this.canceltailor}
  397. style={{
  398. backgroundColor: this.config.cancelButtonBackgroundColor,
  399. color: this.config.cancelButtonTextColor
  400. }}>
  401. {this.config.cancelButtonText}
  402. </div>
  403. <div class={styles.img} onClick={this.rotating}></div>
  404. <div
  405. class={styles.btn}
  406. onClick={this.tailoring}
  407. style={{
  408. backgroundColor: this.config.confirmButtonBackgroundColor,
  409. color: this.config.confirmButtonTextColor
  410. }}>
  411. {this.config.confirmButtonText}
  412. </div>
  413. </div>
  414. ) : null}
  415. </div>
  416. ) : null}
  417. </div>
  418. );
  419. }
  420. });