LandmarkEditor.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <template>
  2. <div class="landmark-editor">
  3. <div class="group-photo-wrapper">
  4. <img
  5. src="@/assets/images/entries/wen-wu-shang-xi.png"
  6. alt=""
  7. class="bg"
  8. draggable="false"
  9. >
  10. <img
  11. ref="person"
  12. class="person"
  13. :src="personImgUrl"
  14. alt=""
  15. draggable="false"
  16. crossOrigin="anonymous"
  17. >
  18. </div>
  19. <div class="button-wrapper">
  20. <div class="line1">
  21. <button
  22. class="give-up"
  23. @click="$router.go(-2)"
  24. >
  25. 放弃
  26. </button>
  27. <button
  28. class="redo"
  29. @click="onClickRedo"
  30. >
  31. 重拍
  32. </button>
  33. </div>
  34. <button
  35. v-if="!isWeixin"
  36. class="save"
  37. @click="onClickSave"
  38. >
  39. 保存
  40. </button>
  41. <button
  42. v-if="isWeixin"
  43. class="generate"
  44. @click="onClickGenerateGroupPhoto"
  45. >
  46. 生成合影
  47. </button>
  48. </div>
  49. <a
  50. v-show="false"
  51. ref="for-download"
  52. :href="aDownloadHref"
  53. download="photo.jpg"
  54. />
  55. <div
  56. v-if="isWeixin"
  57. v-show="isShowResult"
  58. class="resultWrapper"
  59. >
  60. <button
  61. class="close"
  62. @click="isShowResult = false"
  63. >
  64. <img
  65. class=""
  66. src="@/assets/images/close.png"
  67. alt=""
  68. draggable="false"
  69. >
  70. </button>
  71. <img
  72. class="result"
  73. :src="aDownloadHref"
  74. alt=""
  75. draggable="false"
  76. >
  77. <div class="tip">
  78. 长按图片保存
  79. </div>
  80. </div>
  81. <van-popup
  82. v-model="isShowPopup"
  83. class="popup-bottom"
  84. round
  85. position="bottom"
  86. >
  87. <button @click="$router.go(-1)">
  88. 重选场景
  89. </button>
  90. <button @click="onClickReshot">
  91. 重新拍摄
  92. </button>
  93. <button @click="isShowPopup = false">
  94. 取消
  95. </button>
  96. </van-popup>
  97. <input
  98. id="input"
  99. ref="input"
  100. type="file"
  101. accept="image/*"
  102. capture="user"
  103. @input="onInput"
  104. >
  105. </div>
  106. </template>
  107. <script>
  108. const AlloyFinger = require("alloyfinger")
  109. const Transform = require("css3transform")
  110. const To = require("@/utils/to.js")
  111. import html2canvas from "html2canvas"
  112. export default {
  113. data() {
  114. return {
  115. aDownloadHref: '',
  116. isShowPopup: false,
  117. isShowResult: false,
  118. }
  119. },
  120. computed: {
  121. personImgUrl() {
  122. return decodeURI(this.$route.query.personImgUrl)
  123. }
  124. },
  125. mounted() {
  126. function ease(x) {
  127. return Math.sqrt(1 - Math.pow(x - 1, 2))
  128. }
  129. var initScale = 1
  130. const target = this.$refs.person
  131. Transform(target)
  132. new AlloyFinger(target, {
  133. // 多点触摸时重置状态
  134. multipointStart: function() {
  135. To.stopAll()
  136. initScale = target.scaleX
  137. },
  138. // 旋转
  139. rotate: function(evt) {
  140. target.rotateZ += evt.angle
  141. },
  142. // 缩放
  143. pinch: function(evt) {
  144. target.scaleX = target.scaleY = initScale * evt.zoom
  145. },
  146. // 触摸结束时的动画
  147. multipointEnd: function() {
  148. // 使用To.js来管理js开启的动画
  149. To.stopAll()
  150. // // 最小缩放到0.5倍
  151. // if (target.scaleX < 0.5) {
  152. // new To(target, "scaleX", 0.5, 500, ease)
  153. // new To(target, "scaleY", 0.5, 500, ease)
  154. // }
  155. // // 最大2倍
  156. // if (target.scaleX > 2) {
  157. // new To(target, "scaleX", 2, 500, ease)
  158. // new To(target, "scaleY", 2, 500, ease)
  159. // }
  160. // 取旋转角度
  161. var rotation = target.rotateZ % 360
  162. if (rotation < 0) rotation = 360 + rotation
  163. target.rotateZ = rotation
  164. // // 角度回弹
  165. // if (rotation > 0 && rotation < 45) {
  166. // new To(target, "rotateZ", 0, 500, ease)
  167. // } else if (rotation >= 315) {
  168. // new To(target, "rotateZ", 360, 500, ease)
  169. // } else if (rotation >= 45 && rotation < 135) {
  170. // new To(target, "rotateZ", 90, 500, ease)
  171. // } else if (rotation >= 135 && rotation < 225) {
  172. // new To(target, "rotateZ", 180, 500, ease)
  173. // } else if (rotation >= 225 && rotation < 315) {
  174. // new To(target, "rotateZ", 270, 500, ease)
  175. // }
  176. },
  177. // 拖拽
  178. pressMove: function(evt) {
  179. target.translateX += evt.deltaX
  180. target.translateY += evt.deltaY
  181. evt.preventDefault()
  182. }
  183. })
  184. },
  185. methods: {
  186. onClickRedo() {
  187. this.isShowPopup = true
  188. },
  189. garenteeValid(originalFile) { // 确保格式为jpg
  190. return new Promise((resolve) => {
  191. // 选中的图片以dataUrl形式存入FileReader,
  192. let fileReader = new FileReader()
  193. fileReader.readAsDataURL(originalFile)
  194. fileReader.onload = function (loadEvent) {
  195. // 把FileReader中的dataUrl转换成一个Image对象
  196. let image = new Image()
  197. image.src = loadEvent.target.result
  198. image.onload = function () {
  199. // 计算理想尺寸
  200. let newWidth = image.width
  201. let newHeight = image.height
  202. if (image.width >= 2000 || image.height >= 2000) {
  203. if (image.width > image.height) {
  204. newWidth = 1999
  205. newHeight = image.height / image.width * newWidth
  206. } else {
  207. newHeight = 1999
  208. newWidth = image.width / image.height * newHeight
  209. }
  210. }
  211. // 把Image画到canvas上
  212. let canvas = document.createElement("canvas")
  213. // document.body.append(canvas)
  214. // canvas.style.position = 'absolute'
  215. // canvas.style.top = 0
  216. // canvas.style.left = 0
  217. // canvas.style.zIndex = 999999999
  218. canvas.width = newWidth
  219. canvas.height = newHeight
  220. canvas.getContext("2d").drawImage(image, 0, 0, newWidth, newHeight)
  221. // canvas转为jpg格式的blob
  222. canvas.toBlob((blob) => {
  223. // Blob转为File
  224. const fileToSend = new File([blob], `1.jpg`, { type: blob.type })
  225. resolve(fileToSend)
  226. }, 'image/jpeg')
  227. }
  228. }
  229. })
  230. },
  231. onClickReshot() {
  232. this.$refs.input.click()
  233. },
  234. onInput(e) {
  235. if (!e.target.value) {
  236. return
  237. }
  238. const loadingHandler = this.$loading({
  239. lock: true,
  240. text: 'Loading',
  241. spinner: 'el-icon-loading',
  242. background: 'rgba(0, 0, 0, 0.7)',
  243. customClass: 'element-ui-loading'
  244. })
  245. this.garenteeValid(e.target.files[0]).then((jpgFile) => {
  246. return globalApi.getPersonInImage(jpgFile)
  247. }).then((url) => {
  248. loadingHandler.close()
  249. // 不能直接该query.personImgUrl的值,会不生效。
  250. this.$router.replace({
  251. name: this.$route.name,
  252. query: {
  253. personImgUrl: encodeURI(url)
  254. }
  255. })
  256. this.isShowPopup = false
  257. }).catch((err) => {
  258. loadingHandler.close()
  259. console.error(err)
  260. window.alert('请上传包含人物的图片')
  261. })
  262. },
  263. async getGroupPhotoDataUrl() {
  264. const canvas = await html2canvas(document.querySelector('.group-photo-wrapper'), {
  265. useCORS: true, // 【重要】开启跨域配置
  266. scale: 1,
  267. allowTaint: true, // 允许跨域图片
  268. preserveDrawingBuffer: true,
  269. })
  270. this.aDownloadHref = canvas.toDataURL('image/jpeg', 1.0)
  271. },
  272. onClickSave() {
  273. this.getGroupPhotoDataUrl().then(() => {
  274. this.$nextTick(() => {
  275. this.$refs['for-download'].click()
  276. })
  277. })
  278. },
  279. onClickGenerateGroupPhoto() {
  280. this.getGroupPhotoDataUrl().then(() => {
  281. this.isShowResult = true
  282. })
  283. }
  284. }
  285. }
  286. </script>
  287. <style lang="less" scoped>
  288. .landmark-editor {
  289. position: absolute;
  290. left: 0;
  291. top: 0;
  292. right: 0;
  293. bottom: 0;
  294. z-index: 9999;
  295. background: #E7DDD6;
  296. display: flex;
  297. flex-direction: column;
  298. align-items: center;
  299. display: flex;
  300. flex-direction: column;
  301. justify-content: space-evenly;
  302. > .group-photo-wrapper {
  303. width: 92.4vw;
  304. height: fit-content;
  305. overflow: hidden;
  306. position: relative;
  307. > img.bg {
  308. width: 100%;
  309. }
  310. > img.person {
  311. position: absolute;
  312. left: 60%;
  313. top: 40%;
  314. width: 30vw;
  315. }
  316. }
  317. > .button-wrapper {
  318. > .line1 {
  319. display: flex;
  320. justify-content: space-between;
  321. button.give-up {
  322. width: 42.7vw;
  323. height: 16vw;
  324. border-radius: 8vw;
  325. border: 0.3vw solid #A33328;
  326. font-size: 4.3vw;
  327. color: #A33328;
  328. }
  329. button.redo {
  330. width: 42.7vw;
  331. height: 16vw;
  332. border-radius: 8vw;
  333. border: 0.3vw solid #A33328;
  334. font-size: 4.3vw;
  335. color: #A33328;
  336. }
  337. }
  338. > .save, .generate {
  339. margin-top: 4.5vw;
  340. width: 91.1vw;
  341. height: 16vw;
  342. background: #A33328;
  343. border-radius: 8vw;
  344. font-size: 4.3vw;
  345. color: #fff;
  346. }
  347. }
  348. .resultWrapper {
  349. position: absolute;
  350. top: 0;
  351. left: 0;
  352. right: 0;
  353. bottom: 0;
  354. z-index: 1;
  355. background: rgba(0, 0, 0, 0.9);
  356. > button.close {
  357. position: absolute;
  358. top: 2vw;
  359. right: 2vw;
  360. width: 10vw;
  361. height: 10vw;
  362. > img {
  363. width: 100%;
  364. height: 100%;
  365. }
  366. }
  367. > img {
  368. position: absolute;
  369. left: 50%;
  370. top: 50%;
  371. transform: translate(-50%, -50%);
  372. width: 100%;
  373. }
  374. > .tip {
  375. position: absolute;
  376. width: 100%;
  377. text-align: center;
  378. bottom: 5vh;
  379. }
  380. }
  381. > .popup-bottom {
  382. height: 63.1vw;
  383. > button {
  384. height: 33.333%;
  385. width: 100%;
  386. border-bottom: 1px solid #ccc;
  387. color: black;
  388. &:last-of-type {
  389. border-bottom: none;
  390. }
  391. }
  392. }
  393. > #input {
  394. display: none;
  395. }
  396. }
  397. </style>