123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- <template>
- <div
- class="long-image"
- @mousedown="onMouseDown"
- @mousemove="onMouseMove"
- @mouseup="onMouseUp"
- @mouseleave="onMouseLeave"
- @touchstart.passive="onTouchStart"
- @touchmove.prevent="onTouchMove"
- @touchend="onTouchEnd"
- @touchcancel="onTouchCancel"
- @wheel.passive="onWheel"
- >
- <audio
- ref="bgAudio$"
- loop
- autoplay
- :src="bgAudio"
- @play="bgAudioStatus = true"
- @pause="bgAudioStatus = false"
- />
- <div ref="longref$">
- <component
- class="time-item"
- v-for="(timeItem, index) in timeList"
- :key="timeItem.id"
- :is="timeItem.component"
- :info="{ ...timeItem.info, id: timeItem.id }"
- :style="{
- left: `calc(${itemW}% * ${index} - ${translateLength}px)`,
- }"
- @onClickTimeItem="onClickTimeItem"
- />
- </div>
- <Interaction ref="interaction$" :currentTimeIdx="currentTimeIdx" :list="timeList" />
- <Vmenu
- :currentTimeIdx="currentTimeIdx"
- @onClickMenuItem="onClickMenuItem"
- @onClickTimeItem="onClickTimeItem"
- :list="timeList"
- :bgAudioStatus="bgAudioStatus"
- />
- <teleport to="body">
- <Transition>
- <div v-if="isLongImageVideo" class="fade-in-video-wrap">
- <button class="skip-button" v-if="isShowSkip" @click="onSkipClick">
- <img :src="skipImg" alt="" draggable="false">
- </button>
- <button
- class="bofang-button"
- @click="onVideoCanPlayThrough"
- v-if="isNeedToBofang"
- >
- <img :src="bofangImg" alt="" draggable="false">
- </button>
- <video
- ref="video$"
- muted
- autoplay
- :poster="videoPostImg"
- class="initial-video"
- playsinline="true"
- x5-playsinline="true"
- webkit-playsinline="true"
- :src="`${config.cdnDir}videos/video2.mp4`"
- @playing="isNeedToBofang = false"
- @ended="onFadeInVideoEnd"
- @mousedown.passive.stop
- @touchstart.passive.stop
- @canplaythrough="onVideoCanPlayThrough"
- @wheel.passive.stop
- />
- </div>
- </Transition>
- <Transition>
- <div v-if="isShowDir">
- <Directory @close="isShowDir = false" />
- </div>
- </Transition>
- <Transition>
- <div class="guide" v-if="isShowGuide">
- <img :src="guideImg" alt="" draggable="false">
- <div class="tips">
- <p>滚动鼠标滚轮,浏览更多内容</p>
- <img :src="mouseImg" alt="" draggable="false">
- <div @click="onCloseGuide">
- <img :src="comfirImg" alt="" draggable="false">
- </div>
- </div>
- </div>
- </Transition>
- <div v-if="store.currentHotspot">
- <Hotspot @close="store.currentHotspot = ''" />
- </div>
- </teleport>
- </div>
- </template>
- <script setup>
- import { ref, getCurrentInstance, watch, computed, onMounted } from "vue"
- import appStore from "@/store/index";
- import timeList from "@/data/index";
- import Vmenu from "@/components/menu.vue"
- import Directory from "@/components/directory.vue"
- import Hotspot from "@/components/Hotspot.vue"
- import Interaction from "@/components/Interaction.vue"
- import { useRouter } from "vue-router"
- const router = useRouter()
- const store = appStore();
- const isMouseDown = ref(false);
- const lastMoveEventTimeStamp = ref(0);
- const moveSpeed = ref(0);
- const lastTouchPos = ref(0);
- const maxTranslateLength = ref(0);
- const guideImg = utils.getImageUrl(`guide.jpg`)
- const mouseImg = utils.getImageUrl(`mouse.png`)
- const comfirImg = utils.getImageUrl(`btn_concern.png`)
- const skipImg = utils.getImageUrl(`skip.png`)
- const bofangImg = utils.getImageUrl(`bofang.png`)
- const longref$ = ref(null)
- const video$ = ref(null)
- const interaction$ = ref(null)
- // 背景音乐相关
- const bgAudio = utils.getAudioUrl('bg.mp3')
- const bgAudio$ = ref(null)
- onMounted(() => {
- bgAudio$.value.volume = 0.2
- })
- const bgAudioStatus = ref(false)
- function switchBgAudio() {
- if (bgAudio$.value.paused) {
- bgAudio$.value.play()
- } else {
- bgAudio$.value.pause()
- }
- }
- // 过渡视频相关
- const videoPostImg = utils.getImageUrl(`videobg.jpg`)
- const isLongImageVideo = ref(true)
- const isNeedToBofang = ref(true)
- const isShowSkip = ref(false)
- const onVideoCanPlayThrough = () => {
- if (video$.value) {
- video$.value.play()
- isNeedToBofang.value = false
- }
- }
- const onSkipClick = () => {
- isShowGuide.value = true
- setTimeout(() => {
- isLongImageVideo.value = false
- }, 100);
- }
- function onFadeInVideoEnd() {
- isShowGuide.value = true
- setTimeout(() => {
- isLongImageVideo.value = false
- }, 100);
- }
- // 动画帧相关
- const lastAnimationTimeStamp = ref(0);
- const animationFrameId = ref(0);
- // 镜头平移相关
- const translateLength = ref(0);
- const currentTimeIdx = ref(0);
- const instance = getCurrentInstance()
- const globalProperties = instance.appContext.app.config.globalProperties
- const itemW = computed(() => globalProperties.$isMobile || window.innerWidth < 1400 ? 158 : 138)
- const isShowDir = ref(false)
- const isShowGuide = ref(false)
- let firstIn = true
- const onCloseGuide = () => {
- isShowGuide.value = false
- if (firstIn) {
- firstIn = false
- interaction$.value.handleShow()
- store.canPlayLongImageBgAudio = true
- }
- }
- const animationFrameTask = () => {
- const timeStamp = Date.now()
- const timeElapsed = timeStamp - lastAnimationTimeStamp.value
- // 速度减慢
- if (moveSpeed.value > 0) {
- moveSpeed.value -= (globalProperties.$isMobile ? 0.001 : 0.003) * timeElapsed
- if (moveSpeed.value < 0) {
- moveSpeed.value = 0
- }
- } else if (moveSpeed.value < 0) {
- moveSpeed.value += (globalProperties.$isMobile ? 0.001 : 0.003) * timeElapsed
- if (moveSpeed.value > 0) {
- moveSpeed.value = 0
- }
- }
- // 根据速度更新距离
- if (store.canMoveCamera) {
- translateLength.value += moveSpeed.value * timeElapsed
- if (translateLength.value < 0) {
- translateLength.value = 0
- } else if (translateLength.value > maxTranslateLength.value) {
- translateLength.value = maxTranslateLength.value
- moveSpeed.value = 0
- }
- }
- lastAnimationTimeStamp.value = timeStamp
- animationFrameId.value = requestAnimationFrame(animationFrameTask)
- }
- const onClickTimeItem = (index) => {
- translateLength.value = longref$.value.children[0].offsetWidth * index
- console.log('result:', longref$.value.children[0].offsetWidth * index);
- }
- const onClickMenuItem = (item) => {
- if (item.id === 'bgAudio') {
- switchBgAudio()
- } else if (item.id === 'search') {
- isShowDir.value = true
- } else if (item.id === 'tip') {
- isShowGuide.value = true
- } else if (item.id === 'home') {
- router.push({ path: '/' })
- }
- }
- const calcTranslateLimit = () => {
- maxTranslateLength.value = longref$.value.children[0].offsetWidth * (timeList.length - 1)
- }
- const onMouseDown = () => {
- isMouseDown.value = true
- moveSpeed.value = 0
- lastMoveEventTimeStamp.value = 0
- lastAnimationTimeStamp.value = Date.now()
- }
- const onMouseMove = (e) => {
- if (isMouseDown.value) {
- // 有些pc端浏览器比如firefox会有两次事件时间戳相同的情况发生。
- if (lastMoveEventTimeStamp.value && (e.timeStamp - lastMoveEventTimeStamp.value > 1)) {
- // 更新speed
- const currentMoveSpeed = - e.movementX / (e.timeStamp - lastMoveEventTimeStamp.value)
- moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1
- }
- lastMoveEventTimeStamp.value = e.timeStamp
- }
- }
- const onMouseUp = () => {
- isMouseDown.value = false
- }
- const onMouseLeave = () => {
- isMouseDown.value = false
- }
- const onTouchStart = (e) => {
- isMouseDown.value = true
- moveSpeed.value = 0
- lastMoveEventTimeStamp.value = 0
- lastAnimationTimeStamp.value = Date.now()
- lastTouchPos.value = (globalProperties.$isRotate ? e.changedTouches[0].clientY : e.changedTouches[0].clientX)
- }
- const onTouchMove = (e) => {
- if (isMouseDown.value && e.changedTouches.length === 1) {
- // 疯狂操作的极端情况下两个时间戳之间的时差会不合理,甚至为0
- if (lastMoveEventTimeStamp.value && (e.timeStamp - lastMoveEventTimeStamp.value > 1)) {
- // 更新speed
- const currentMoveSpeed = - ((globalProperties.$isRotate ? e.changedTouches[0].clientY : e.changedTouches[0].clientX) - lastTouchPos.value) / (e.timeStamp - lastMoveEventTimeStamp.value) * (globalProperties.$isFirefox ? 2.2 : 1.5)
- moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1
- lastTouchPos.value = globalProperties.$isRotate ? e.changedTouches[0].clientY : e.changedTouches[0].clientX
- }
- lastMoveEventTimeStamp.value = e.timeStamp
- }
- }
- const onTouchEnd = () => {
- isMouseDown.value = false
- }
- const onTouchCancel = () => {
- isMouseDown.value = false
- }
- const onWheel = (e) => {
- if (store.canMoveCamera) {
- translateLength.value += e.deltaY
- if (translateLength.value < 0) {
- translateLength.value = 0
- } else if (translateLength.value > maxTranslateLength.value) {
- translateLength.value = maxTranslateLength.value
- moveSpeed.value = 0
- }
- }
- }
- watch(translateLength, (vNew) => {
- try {
- currentTimeIdx.value = Math.round(translateLength.value / longref$.value.children[0].offsetWidth)
- } catch (error) {
- console.error('translateLength error: ', error)
- }
- })
- onMounted(() => {
- animationFrameId.value = requestAnimationFrame(animationFrameTask)
- if (store.longImageTranslateLengthRecord) {
- translateLength.value = store.longImageTranslateLengthRecord
- store.longImageTranslateLengthRecord = null
- }
- calcTranslateLimit()
- window.addEventListener('resize', calcTranslateLimit)
- setTimeout(() => {
- isShowSkip.value = true
- }, 6000);
- })
- </script>
- <style lang="scss" scoped>
- .long-image {
- height: 100%;
- width: 100%;
- position: relative;
- overflow: hidden;
- .time-item {
- position: absolute;
- top: 0;
- height: 100%;
- width: calc(v-bind(itemW) * 1%);
- max-width: calc(v-bind(itemW) * 1%);
- text-align: center;
- justify-content: flex-start;
- display: flex;
- align-items: center;
- }
- }
- .guide {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- top: 0;
- width: 100%;
- height: 100%;
- z-index: 99;
- background-color: rgba($color: #000000, $alpha: 0.65);
- backdrop-filter: blur(15px);
- display: flex;
- justify-content: center;
- align-items: flex-end;
- >img {
- width: 100%;
- }
- .tips {
- position: absolute;
- top: 25%;
- left: 50%;
- transform: translateX(-50%);
- z-index: 100;
- text-align: center;
- color: #fff;
- width: 20%;
- >img {
- margin: 1.63rem 0 1rem;
- width: 80%;
- }
- >div {
- cursor: pointer;
- >img {
- width: 7rem;
- }
- }
- }
- }
- .fade-in-video-wrap {
- .skip-button {
- position: absolute;
- top: 1.21rem;
- right: 1.46rem;
- height: 1.75rem;
- z-index: 10000;
- img {
- height: 100%;
- }
- }
- .bofang-button {
- position: absolute;
- left: 50%;
- bottom: 30%;
- transform: translateX(-50%);
- z-index: 10000;
- width: 6rem;
- img {
- width: 100%;
- }
- }
- .initial-video {
- position: absolute; // 微信内嵌浏览器里视频必须决定对定位且z-index为负,否则,开始播放后,应该层叠在其上的决对定位的元素不会显示。
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: #1f0f05;
- object-fit: cover;
- }
- }
- </style>
|