index.tsx 15 KB

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