home.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <template>
  2. <div class="home" @mousedown="onMouseDown" @mousemove="onMouseMove" @mouseup="onMouseUp" @mouseleave="onMouseLeave"
  3. @touchstart.passive="onTouchStart" @touchmove.prevent="onTouchMove" @touchend="onTouchEnd"
  4. @touchcancel="onTouchCancel" @wheel.passive="onWheel">
  5. <div ref="hcon$" class="h-con" :style="{
  6. left: `-${translateLength}px`,
  7. }">
  8. <div class="h-left">
  9. <img draggable="false" :src="$getImageUrl('h-left.jpg')" alt="">
  10. <div class="l-con">
  11. <img draggable="false" class="title" :src="$getImageUrl('title.png')" alt="">
  12. </div>
  13. </div>
  14. <div class="h-right">
  15. <ul>
  16. <li v-for="(item, idx) in list" :key="item.id">
  17. <img @click="onClickItem(item)" draggable="false" class="title" :src="$getImageUrl(`home-img/${idx + 1}.png`)"
  18. alt="">
  19. </li>
  20. </ul>
  21. </div>
  22. <img @click="isShowCR = true" draggable="false" class="menu" :src="$getImageUrl('menu.png')" alt="">
  23. </div>
  24. <teleport to='body'>
  25. <Transition>
  26. <CulturalRelic @close="isShowCR = ''" v-if="isShowCR" />
  27. </Transition>
  28. <Transition>
  29. <TwoDetail :data="current" @close="current = ''" v-if="current && current.isTwo" />
  30. </Transition>
  31. <Transition>
  32. <ThreeDetail :data="current" @close="current = ''" v-if="current && !current.isTwo" />
  33. </Transition>
  34. </teleport>
  35. </div>
  36. </template>
  37. <script setup>
  38. import { onBeforeUnmount, onMounted, ref } from "vue"
  39. import TwoDetail from "@/components/detail/TwoDetail.vue";
  40. import ThreeDetail from "@/components/detail/ThreeDetail.vue";
  41. import CulturalRelic from "@/views/cultural-relic.vue"
  42. import { two, three } from "@/data/category";
  43. let list = three.data.concat(two.data.map(item => {
  44. return { ...item, isTwo: true }
  45. }))
  46. console.log('result:', list);
  47. const isMouseDown = ref(false);
  48. const lastMoveEventTimeStamp = ref(0);
  49. const moveSpeed = ref(0);
  50. const lastTouchPos = ref(0);
  51. const maxTranslateLength = ref(0);
  52. // 动画帧相关
  53. const lastAnimationTimeStamp = ref(0);
  54. const animationFrameId = ref(0);
  55. const isShowCR = ref('');
  56. const current = ref('')
  57. const isMove = ref(false)
  58. // 镜头平移相关
  59. const translateLength = ref(0);
  60. const hcon$ = ref(null)
  61. const onClickItem = (item) => {
  62. if (!isMove.value) {
  63. current.value = item
  64. }
  65. }
  66. const animationFrameTask = () => {
  67. const timeStamp = Date.now()
  68. const timeElapsed = timeStamp - lastAnimationTimeStamp.value
  69. // 速度减慢
  70. if (moveSpeed.value > 0) {
  71. moveSpeed.value -= 0.003 * timeElapsed
  72. if (moveSpeed.value < 0) {
  73. moveSpeed.value = 0
  74. }
  75. } else if (moveSpeed.value < 0) {
  76. moveSpeed.value += 0.003 * timeElapsed
  77. if (moveSpeed.value > 0) {
  78. moveSpeed.value = 0
  79. }
  80. }
  81. // 根据速度更新距离
  82. translateLength.value += moveSpeed.value * timeElapsed
  83. if (translateLength.value < 0) {
  84. translateLength.value = 0
  85. } else if (translateLength.value > maxTranslateLength.value) {
  86. translateLength.value = maxTranslateLength.value
  87. moveSpeed.value = 0
  88. }
  89. lastAnimationTimeStamp.value = timeStamp
  90. animationFrameId.value = requestAnimationFrame(animationFrameTask)
  91. }
  92. const calcTranslateLimit = () => {
  93. maxTranslateLength.value = hcon$.value.clientWidth - window.innerWidth
  94. }
  95. const onMouseDown = () => {
  96. isMouseDown.value = true
  97. moveSpeed.value = 0
  98. lastMoveEventTimeStamp.value = 0
  99. lastAnimationTimeStamp.value = Date.now()
  100. }
  101. const onMouseMove = (e) => {
  102. if (isMouseDown.value) {
  103. // 有些pc端浏览器比如firefox会有两次事件时间戳相同的情况发生。
  104. if (lastMoveEventTimeStamp.value && (e.timeStamp - lastMoveEventTimeStamp.value > 1)) {
  105. isMove.value = true
  106. // 更新speed
  107. const currentMoveSpeed = - e.movementX / (e.timeStamp - lastMoveEventTimeStamp.value)
  108. moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1
  109. }
  110. lastMoveEventTimeStamp.value = e.timeStamp
  111. }
  112. }
  113. const onMouseUp = () => {
  114. isMouseDown.value = false
  115. setTimeout(() => {
  116. isMove.value = false
  117. });
  118. }
  119. const onMouseLeave = () => {
  120. isMouseDown.value = false
  121. setTimeout(() => {
  122. isMove.value = false
  123. });
  124. }
  125. const onTouchStart = (e) => {
  126. isMouseDown.value = true
  127. moveSpeed.value = 0
  128. lastMoveEventTimeStamp.value = 0
  129. lastAnimationTimeStamp.value = Date.now()
  130. lastTouchPos.value = e.changedTouches[0].clientX
  131. }
  132. const onTouchMove = (e) => {
  133. if (isMouseDown.value && e.changedTouches.length === 1) {
  134. // 疯狂操作的极端情况下两个时间戳之间的时差会不合理,甚至为0
  135. if (lastMoveEventTimeStamp.value && (e.timeStamp - lastMoveEventTimeStamp.value > 1)) {
  136. // 更新speed
  137. isMove.value = true
  138. const currentMoveSpeed = - (e.changedTouches[0].clientX - lastTouchPos.value) / (e.timeStamp - lastMoveEventTimeStamp.value) * 1.5
  139. moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1
  140. lastTouchPos.value = e.changedTouches[0].clientX
  141. }
  142. lastMoveEventTimeStamp.value = e.timeStamp
  143. }
  144. }
  145. const onTouchEnd = () => {
  146. isMouseDown.value = false
  147. setTimeout(() => {
  148. isMove.value = false
  149. });
  150. }
  151. const onTouchCancel = () => {
  152. isMouseDown.value = false
  153. setTimeout(() => {
  154. isMove.value = false
  155. });
  156. }
  157. const onWheel = (e) => {
  158. translateLength.value += e.deltaY
  159. if (translateLength.value < 0) {
  160. translateLength.value = 0
  161. } else if (translateLength.value > maxTranslateLength.value) {
  162. translateLength.value = maxTranslateLength.value
  163. moveSpeed.value = 0
  164. }
  165. }
  166. onMounted(() => {
  167. animationFrameId.value = requestAnimationFrame(animationFrameTask)
  168. window.addEventListener('resize', calcTranslateLimit)
  169. calcTranslateLimit()
  170. document.onreadystatechange = () => {
  171. if (document.readyState === 'complete') {
  172. // 页面上所有资源加载完成后执行的逻辑
  173. console.log('所有资源加载完成');
  174. calcTranslateLimit()
  175. }
  176. };
  177. })
  178. onBeforeUnmount(() => {
  179. document.onreadystatechange = null
  180. window.removeEventListener('resize', calcTranslateLimit)
  181. })
  182. </script>
  183. <style lang="less" scoped>
  184. .home {
  185. // background-image: url('@/assets/images/bg-top.png');
  186. height: 100%;
  187. width: 100%;
  188. .h-con {
  189. height: 100%;
  190. display: flex;
  191. width: max-content;
  192. position: relative;
  193. .h-left {
  194. height: 100%;
  195. position: relative;
  196. width: max-content;
  197. >img {
  198. height: 100%;
  199. }
  200. .l-con {
  201. .logo {
  202. position: absolute;
  203. left: 1rem;
  204. top: 1rem;
  205. width: 16%;
  206. }
  207. .title {
  208. position: absolute;
  209. left: 50%;
  210. top: 50%;
  211. width: 70%;
  212. transform: translate(-50%, -50%);
  213. }
  214. }
  215. }
  216. .h-right {
  217. background-image: url('@/assets/images/bg-top.jpg');
  218. background-repeat: no-repeat;
  219. background-size: auto 80%;
  220. background-color: #dddad1;
  221. background-position: 0 0;
  222. height: 100%;
  223. position: relative;
  224. font-size: 0;
  225. user-select: none;
  226. &::after {
  227. width: 100%;
  228. position: absolute;
  229. bottom: 0;
  230. content: '';
  231. display: inline-block;
  232. height: 20%;
  233. box-sizing: border-box;
  234. border-top: 2rem solid #8B8980;
  235. background-image: linear-gradient(180deg, #93918A 0%, #AAA89F 63%);
  236. }
  237. >ul {
  238. height: 100%;
  239. position: relative;
  240. z-index: 999;
  241. display: flex;
  242. align-items: flex-end;
  243. padding-left: 10rem;
  244. margin-bottom: 10rem;
  245. >li {
  246. list-style: none;
  247. margin: 0 6rem;
  248. >img {
  249. max-width: 80%;
  250. cursor: pointer;
  251. }
  252. }
  253. }
  254. }
  255. }
  256. }
  257. .menu{
  258. position: fixed;
  259. right: 2rem;
  260. top: 2rem;
  261. width: 3%;
  262. cursor: pointer;
  263. z-index: 999;
  264. }
  265. </style>