HomeView.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <template>
  2. <div
  3. class="home"
  4. @mousedown="onMouseDown"
  5. @mousemove="onMouseMove"
  6. @mouseup="onMouseUp"
  7. @mouseleave="onMouseLeave"
  8. @wheel.passive="onWheel"
  9. @dragstart.prevent
  10. >
  11. <div
  12. ref="landscape-wrap"
  13. class="landscape-wrap"
  14. :style="{
  15. left: landscapePositionleft,
  16. }"
  17. >
  18. <img
  19. v-for="index in 3"
  20. :key="index"
  21. class="landscape"
  22. src="@/assets/landscape.png"
  23. alt=""
  24. @dragstart.prevent
  25. >
  26. </div>
  27. <div
  28. class="people-far-wrap"
  29. :style="{
  30. right: peopleFarPositionRight,
  31. }"
  32. >
  33. <img
  34. class="people-far"
  35. :class="peopleFarColorStatus"
  36. src="@/assets/people-far-serial-frame-400-600-60.png"
  37. alt=""
  38. @dragstart.prevent
  39. >
  40. <HotSpot
  41. class="hot-spot"
  42. @click="onClickPeopleFarHotSpot"
  43. />
  44. </div>
  45. <div
  46. class="people-near-wrap"
  47. :style="{
  48. left: peopleNearPositionLeft,
  49. zIndex: [0, 2].includes(tourState) ? 2 : 0,
  50. }"
  51. >
  52. <img
  53. class="people-near"
  54. src="@/assets/people-near.png"
  55. alt=""
  56. @dragstart.prevent
  57. >
  58. <img
  59. v-if="[0, 2].includes(tourState)"
  60. ref="treasure"
  61. class="treasure"
  62. :style="{
  63. opacity: tourState === 0 ?
  64. treasureFadeInProgress / treasureFadeInProgressRightBorder :
  65. 1 - treasureFadeOutProgress / treasureFadeOutProgressRightBorder,
  66. }"
  67. src="@/assets/treasure.png"
  68. alt=""
  69. @dragstart.prevent
  70. >
  71. </div>
  72. <img
  73. class="introduce"
  74. :style="{
  75. left: introducePositionLeft,
  76. }"
  77. src="@/assets/introduce.png"
  78. alt=""
  79. @dragstart.prevent
  80. >
  81. <div
  82. v-if="[0, 1, 2].includes(tourState)"
  83. class="fade-mask"
  84. :style="{
  85. opacity: tourState === 0 ?
  86. treasureFadeInProgress / treasureFadeInProgressRightBorder :
  87. 1 - treasureFadeOutProgress / treasureFadeOutProgressRightBorder
  88. }"
  89. />
  90. <div
  91. v-if="[1].includes(tourState)"
  92. class="treasure-frames-wrap"
  93. >
  94. <img
  95. v-for="index of treasureFrameTotalNum"
  96. v-show="treasureFrameCurNum === index - 1 ||
  97. treasureFrameCurNum - 1 === index - 1
  98. "
  99. :key="index"
  100. class="treasure-frame"
  101. :src="require(`@/assets/treasure-frames/文物序列帧-${(index-1).toString().padStart(7, '0')}.png`)"
  102. alt=""
  103. @load="onTreasureFrameLoad(index - 1)"
  104. @error="onTreasureFrameError(index - 1)"
  105. >
  106. </div>
  107. </div>
  108. </template>
  109. <script>
  110. const initialPeopleNearPositionLeft = '25%'
  111. const initPeopleFarPositionRight = '10%'
  112. const landscapeSpeedRate = 0.01
  113. const peopleFarSpeedRate = 0.2
  114. const peopleNearSpeedRate = 0.8
  115. const introduceSpeedRate = 1
  116. const treasureFadeInProgressRightBorder = 2000
  117. const treasureDisplayProgressRightBorder = 3000
  118. const treasureFadeOutProgressRightBorder = 2000
  119. const treasureFrameTotalNum = 100
  120. export default {
  121. name: 'HomeView',
  122. components: {
  123. },
  124. data() {
  125. return {
  126. // 鼠标拖拽相关
  127. isMouseDown: false,
  128. lastMoveEventTimeStamp: 0,
  129. moveSpeed: 0,
  130. // 动画帧相关
  131. lastAnimationTimeStamp: 0,
  132. animationFrameId: null,
  133. tourState: 0, // 0:文物淡入过渡阶段;1:文物三维展示阶段;2:文物渐出过渡阶段;3:镜头平移阶段
  134. // 镜头平移相关
  135. translateLength: 0,
  136. landscapePositionleft: '18.491%',
  137. peopleFarPositionRight: initPeopleFarPositionRight,
  138. peopleNearPositionLeft: initialPeopleNearPositionLeft,
  139. introducePositionLeft: '3.347%',
  140. // 文物淡入相关
  141. treasureFadeInProgress: 0,
  142. treasureFadeInProgressRightBorder,
  143. treatureFadeInInitialLeft: 0,
  144. treatureFadeInInitialTop: 0,
  145. treatureFadeInFinalLeft: 0,
  146. treatureFadeInFinalTop: 0,
  147. // 文物展示相关
  148. treasureDisplayProgress: 0,
  149. treasureFrameTotalNum,
  150. treasureFrameCurNum: 0,
  151. treasureFrameStateList: new Array(treasureFrameTotalNum),
  152. // 文物淡出相关
  153. treasureFadeOutProgress: 0,
  154. treasureFadeOutProgressRightBorder,
  155. // 远处人物变色相关
  156. peopleFarColorStatus: 'no-color', // 'no-color', 'color'
  157. isPeopleFarColorChanging: false,
  158. }
  159. },
  160. watch: {
  161. treasureFadeInProgress: {
  162. handler(vNew, vOld) {
  163. if (vOld < this.treasureFadeInProgressRightBorder && vNew >= this.treasureFadeInProgressRightBorder && this.tourState === 0) {
  164. this.tourState = 1
  165. }
  166. this.$refs.treasure.style.left = this.treatureFadeInInitialLeft + this.treasureFadeInProgress / treasureFadeInProgressRightBorder * (this.treatureFadeInFinalLeft - this.treatureFadeInInitialLeft) + 'px'
  167. this.$refs.treasure.style.top = this.treatureFadeInInitialTop + this.treasureFadeInProgress / treasureFadeInProgressRightBorder * (this.treatureFadeInFinalTop - this.treatureFadeInInitialTop) + 'px'
  168. this.$refs.treasure.style.transform = `translate(-50%, -50%) scale(${1 + this.treasureFadeInProgress / treasureFadeInProgressRightBorder * (10 - 1)})`
  169. }
  170. },
  171. treasureDisplayProgress: {
  172. handler(vNew, vOld) {
  173. // 更新toureState
  174. if (vOld > 0 && vNew <= 0 && this.tourState === 1) {
  175. this.tourState = 0
  176. } else if (vOld < treasureDisplayProgressRightBorder && vNew >= treasureDisplayProgressRightBorder && this.tourState === 1) {
  177. this.tourState = 2
  178. }
  179. let idealCurNum = Math.round(this.treasureDisplayProgress / treasureDisplayProgressRightBorder * treasureFrameTotalNum)
  180. while (this.treasureFrameStateList[idealCurNum] === false) {
  181. idealCurNum--
  182. }
  183. this.treasureFrameCurNum = idealCurNum
  184. }
  185. },
  186. treasureFadeOutProgress: {
  187. handler(vNew, vOld) {
  188. if (vOld > 0 && vNew <= 0 && this.tourState === 2) {
  189. this.tourState = 1
  190. } else if (vOld < this.treasureFadeOutProgressRightBorder && vNew >= this.treasureFadeOutProgressRightBorder && this.tourState === 2) {
  191. this.tourState = 3
  192. }
  193. console.log('fadeOut阶段!', this.treasureFadeOutProgress)
  194. }
  195. },
  196. translateLength: {
  197. handler(vNew, vOld) {
  198. // const rightBorder = window.innerWidth * 2
  199. if (vOld < 0 && vNew >= 0 && this.tourState === 3) {
  200. this.tourState = 2
  201. }
  202. // if (vOld > -rightBorder && vNew <= -rightBorder && this.tourState === 3) {
  203. // this.tourState = 1
  204. // }
  205. this.landscapePositionleft = `calc(18.491% + ${vNew * landscapeSpeedRate}px)`
  206. this.peopleFarPositionRight = `calc(${initPeopleFarPositionRight} - ${vNew * peopleFarSpeedRate}px)`
  207. this.peopleNearPositionLeft = `calc(${initialPeopleNearPositionLeft} + ${vNew * peopleNearSpeedRate}px)`
  208. this.introducePositionLeft = `calc(3.347% + ${vNew * introduceSpeedRate}px)`
  209. console.log('平移阶段!', this.translateLength)
  210. }
  211. },
  212. },
  213. mounted() {
  214. this.animationFrameId = requestAnimationFrame(this.inertanceEffect)
  215. this.treatureFadeInInitialLeft = this.$refs.treasure.offsetLeft
  216. this.treatureFadeInInitialTop = this.$refs.treasure.offsetTop
  217. this.treatureFadeInFinalLeft = window.innerWidth / 2 - this.$refs.treasure.offsetParent.offsetLeft
  218. this.treatureFadeInFinalTop = window.innerHeight / 2 - this.$refs.treasure.offsetParent.offsetTop
  219. },
  220. unmounted() {
  221. cancelAnimationFrame(this.animationFrameId)
  222. },
  223. methods: {
  224. onMouseDown(e) {
  225. this.isMouseDown = true
  226. this.moveSpeed = 0
  227. this.lastMoveEventTimeStamp = 0
  228. this.lastAnimationTimeStamp = Date.now()
  229. },
  230. onMouseUp(e) {
  231. this.isMouseDown = false
  232. },
  233. onMouseLeave() {
  234. this.isMouseDown = false
  235. },
  236. onMouseMove(e) {
  237. if (this.isMouseDown) {
  238. if (this.lastMoveEventTimeStamp) {
  239. // 更新speed
  240. const currentMoveSpeed = e.movementX / (e.timeStamp - this.lastMoveEventTimeStamp)
  241. this.moveSpeed = this.moveSpeed * 0.9 + currentMoveSpeed * 0.1
  242. }
  243. this.lastMoveEventTimeStamp = e.timeStamp
  244. }
  245. },
  246. onWheel(e) {
  247. if (this.tourState === 0) {
  248. this.treasureFadeInProgress += e.deltaY
  249. if (this.treasureFadeInProgress < 0) {
  250. this.treasureFadeInProgress = 0
  251. this.moveSpeed = 0
  252. } else if (this.treasureFadeInProgress > this.treasureFadeInProgressRightBorder) {
  253. this.treasureFadeInProgress = this.treasureFadeInProgressRightBorder
  254. }
  255. } else if (this.tourState === 1) {
  256. this.treasureDisplayProgress += e.deltaY
  257. if (this.treasureDisplayProgress < 0) {
  258. this.treasureDisplayProgress = 0
  259. } else if (this.treasureDisplayProgress > treasureDisplayProgressRightBorder) {
  260. this.treasureDisplayProgress = treasureDisplayProgressRightBorder
  261. }
  262. } else if (this.tourState === 2) {
  263. this.treasureFadeOutProgress += e.deltaY
  264. if (this.treasureFadeOutProgress < 0) {
  265. this.treasureFadeOutProgress = 0
  266. } else if (this.treasureFadeOutProgress > this.treasureFadeOutProgressRightBorder) {
  267. this.treasureFadeOutProgress = this.treasureFadeOutProgressRightBorder
  268. }
  269. } else if (this.tourState === 3) {
  270. this.translateLength -= e.deltaY
  271. if (this.translateLength > 0) {
  272. this.translateLength = 0
  273. }
  274. }
  275. },
  276. inertanceEffect() {
  277. const timeStamp = Date.now()
  278. const timeElapsed = timeStamp - this.lastAnimationTimeStamp
  279. // 速度减慢
  280. if (this.moveSpeed > 0) {
  281. this.moveSpeed -= 0.003 * timeElapsed
  282. if (this.moveSpeed < 0) {
  283. this.moveSpeed = 0
  284. }
  285. } else if (this.moveSpeed < 0) {
  286. this.moveSpeed += 0.003 * timeElapsed
  287. if (this.moveSpeed > 0) {
  288. this.moveSpeed = 0
  289. }
  290. }
  291. // 根据速度更新“距离”
  292. if (this.tourState === 0) {
  293. this.treasureFadeInProgress -= this.moveSpeed * timeElapsed
  294. if (this.treasureFadeInProgress < 0) {
  295. this.treasureFadeInProgress = 0
  296. this.moveSpeed = 0
  297. } else if (this.treasureFadeInProgress > this.treasureFadeInProgressRightBorder) {
  298. this.treasureFadeInProgress = this.treasureFadeInProgressRightBorder
  299. }
  300. } else if (this.tourState === 1) {
  301. this.treasureDisplayProgress -= this.moveSpeed * timeElapsed
  302. if (this.treasureDisplayProgress < 0) {
  303. this.treasureDisplayProgress = 0
  304. } else if (this.treasureDisplayProgress > treasureDisplayProgressRightBorder) {
  305. this.treasureDisplayProgress = treasureDisplayProgressRightBorder
  306. }
  307. } else if (this.tourState === 2) {
  308. this.treasureFadeOutProgress -= this.moveSpeed * timeElapsed
  309. if (this.treasureFadeOutProgress < 0) {
  310. this.treasureFadeOutProgress = 0
  311. } else if (this.treasureFadeOutProgress > this.treasureFadeOutProgressRightBorder) {
  312. this.treasureFadeOutProgress = this.treasureFadeOutProgressRightBorder
  313. }
  314. } else if (this.tourState === 3) {
  315. this.translateLength += this.moveSpeed * timeElapsed
  316. if (this.translateLength > 0) {
  317. this.translateLength = 0
  318. }
  319. }
  320. this.lastAnimationTimeStamp = timeStamp
  321. this.animationFrameId = requestAnimationFrame(this.inertanceEffect)
  322. },
  323. onClickPeopleFarHotSpot() {
  324. if (this.isPeopleFarColorChanging) {
  325. return
  326. } else {
  327. if (this.peopleFarColorStatus === 'no-color') {
  328. this.peopleFarColorStatus = 'color'
  329. } else {
  330. this.peopleFarColorStatus = 'no-color'
  331. }
  332. this.isPeopleFarColorChanging = true
  333. setTimeout(() => {
  334. this.isPeopleFarColorChanging = false
  335. }, 2500)
  336. }
  337. },
  338. onTreasureFrameLoad(idx) {
  339. this.treasureFrameStateList[idx] = true
  340. },
  341. onTreasureFrameError(idx) {
  342. this.treasureFrameStateList[idx] = false
  343. },
  344. }
  345. }
  346. </script>
  347. <style lang="less" scoped>
  348. .home {
  349. width: 100%;
  350. height: 100%;
  351. background-image: url(@/assets/background.jpg);
  352. background-repeat: repeat;
  353. background-size: contain;
  354. position: relative;
  355. overflow: hidden;
  356. .landscape-wrap {
  357. position: absolute;
  358. height: 30%;
  359. top: 0;
  360. display: flex;
  361. > .landscape {
  362. height: 100%;
  363. }
  364. }
  365. > .people-far-wrap {
  366. position: absolute;
  367. top: 25%;
  368. height: 55vh;
  369. width: calc(55vh * 400 / 600);
  370. overflow: hidden;
  371. > .people-far {
  372. position: absolute;
  373. height: 100%;
  374. transition-property: left;
  375. transition-duration: 2.5s;
  376. transition-timing-function: steps(59, jump-end);
  377. &.no-color {
  378. left: 0;
  379. }
  380. &.color {
  381. left: calc(-100% * 59)
  382. }
  383. }
  384. > .hot-spot {
  385. position: absolute;
  386. left: 50%;
  387. transform: translateX(-50%);
  388. top: 10%;
  389. }
  390. }
  391. .people-near-wrap {
  392. position: absolute;
  393. bottom: -13%;
  394. height: 100vh;
  395. overflow: hidden;
  396. > .people-near {
  397. height: 100%;
  398. }
  399. > .treasure {
  400. position: absolute;
  401. left: 40%;
  402. top: 35%;
  403. width: 7%;
  404. height: 3.5%;
  405. }
  406. }
  407. .introduce {
  408. position: absolute;
  409. top: 5%;
  410. width: 13.727%;
  411. }
  412. .fade-mask {
  413. position: absolute;
  414. top: 0;
  415. bottom: 0;
  416. left: 0;
  417. right: 0;
  418. background-color: #663e21;
  419. z-index: 1;
  420. }
  421. .treasure-frames-wrap {
  422. position: absolute;
  423. top: 50%;
  424. left: 50%;
  425. transform: translate(-50%, -50%);
  426. z-index: 3;
  427. img {
  428. position: absolute;
  429. }
  430. }
  431. }
  432. </style>