index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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: 'o-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. const box = document.getElementsByClassName('cropper-crop-box')[0]
  79. //左网格线
  80. const 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. const 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. const 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. const 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. const 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. const 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. const 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. const 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. const 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. const 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. const 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. const 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. const 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 (!/^[\/]?([\da-zA-Z]+[\/+]+)*[\da-zA-Z]+([+=]{1,2}|[\/])?$/.test(base[1])) {
  262. throw new Error('Not standard base64')
  263. }
  264. this.img = base64
  265. setTimeout(() => {
  266. this.config.autoCrop = true
  267. this.addSlide()
  268. }, 10)
  269. },
  270. rotating(e: any) {
  271. ;(this as any).$refs.cropper.rotateRight()
  272. // document.getElementsByClassName("cropper-modal")[0].style = "background-color: rgba(0,0,0,0.5);transition: 0.88s";
  273. },
  274. canceltailor() {
  275. this.img = ''
  276. this.onCancelTailor()
  277. },
  278. tailoring() {
  279. // 获取截图的base64数据
  280. ;(this as any).$refs.cropper.getCropData((data: any) => {
  281. this.getBase64Data(data)
  282. this.getBlob(data)
  283. this.img = ''
  284. this.config.autoCrop = false
  285. })
  286. // 获取截图的blob数据
  287. ;(this as any).$refs.cropper.getCropBlob((data: BlobPart) => {
  288. this.getBase64Data(data)
  289. this.getBlob(data)
  290. // Blob 转 File
  291. const suffix = {
  292. jpeg: 'jpg',
  293. png: 'png',
  294. webp: 'webp'
  295. }[this.config.outputType]
  296. const time = new Date().getTime()
  297. const file = new File([data], `${time}.${suffix}`, {
  298. type: `image/${this.config.outputType}`
  299. })
  300. this.getFile(file)
  301. this.img = ''
  302. this.config.autoCrop = false
  303. })
  304. },
  305. async upPhoto(e: any) {
  306. let photoUrl = e.target.files[0]
  307. ;(this as any).$refs.headInput.value = null
  308. if (photoUrl != undefined) {
  309. this.imgOriginF(photoUrl)
  310. this.img = (await this.onLoadImg(photoUrl)) as string
  311. this.config.autoCrop = true
  312. setTimeout(() => {
  313. this.addSlide()
  314. }, 20)
  315. }
  316. },
  317. onCropMoving(e: any) {
  318. // console.log('onCropMoving')
  319. },
  320. onImgMoving(e: any) {
  321. // console.log('onCropMoving')
  322. }
  323. },
  324. render() {
  325. return (
  326. <div class={[styles.upbtn, styles.uploadWarper]}>
  327. {this.hideInput}
  328. {!this.hideInput ? (
  329. <input
  330. style="opacity: 0;"
  331. class={styles.upbtn}
  332. type="file"
  333. accept="image/*"
  334. onChange={this.upPhoto}
  335. ref="headInput"
  336. />
  337. ) : null}
  338. {this.img != '' ? (
  339. <div class={styles.bg}>
  340. {this.config.ceilbutton ? (
  341. <div class={styles.btndiv}>
  342. <div
  343. class={styles.btn}
  344. onClick={this.canceltailor}
  345. style={{
  346. backgroundColor: this.config.cancelButtonBackgroundColor,
  347. color: this.config.cancelButtonTextColor
  348. }}
  349. >
  350. {this.config.cancelButtonText}
  351. </div>
  352. <div class={styles.img} onClick={this.rotating}></div>
  353. <div
  354. class={styles.btn}
  355. onClick={this.tailoring}
  356. style={{
  357. backgroundColor: this.config.confirmButtonBackgroundColor,
  358. color: this.config.confirmButtonTextColor
  359. }}
  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}
  391. ></VueCropper>
  392. </div>
  393. {!this.config.ceilbutton ? (
  394. <div class={styles.btndiv}>
  395. <div
  396. class={styles.btn}
  397. onClick={this.canceltailor}
  398. style={{
  399. backgroundColor: this.config.cancelButtonBackgroundColor,
  400. color: this.config.cancelButtonTextColor
  401. }}
  402. >
  403. {this.config.cancelButtonText}
  404. </div>
  405. <div class={styles.img} onClick={this.rotating}></div>
  406. <div
  407. class={styles.btn}
  408. onClick={this.tailoring}
  409. style={{
  410. backgroundColor: this.config.confirmButtonBackgroundColor,
  411. color: this.config.confirmButtonTextColor
  412. }}
  413. >
  414. {this.config.confirmButtonText}
  415. </div>
  416. </div>
  417. ) : null}
  418. </div>
  419. ) : null}
  420. </div>
  421. )
  422. }
  423. })