HomeView.vue 14 KB

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