|
|
@@ -1,5 +1,214 @@
|
|
|
<template>
|
|
|
- <div class="bg-neutral-900 w-full h-full"></div>
|
|
|
+ <div class="bg-neutral-900 w-full h-full relative">
|
|
|
+ <video
|
|
|
+ ref="video"
|
|
|
+ playsinline
|
|
|
+ src="https://zm-shorts.oss-cn-hangzhou.aliyuncs.com/uploads/cltreekiv0003lr8i0p93a5h5.mp4"
|
|
|
+ poster="https://zm-shorts.oss-cn-hangzhou.aliyuncs.com/uploads/cltreg5k20005lr8i8obwfy11.png"
|
|
|
+ :controls="false"
|
|
|
+ class="object-contain w-full h-full"
|
|
|
+ @timeupdate="onTimeUpdate"
|
|
|
+ ></video>
|
|
|
+ <div
|
|
|
+ v-show="!draggingData.dragging"
|
|
|
+ class="mask-info absolute w-full h-full top-0 left-0"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="title-info absolute bottom-0 left-0 right-0 drop-shadow px-4"
|
|
|
+ >
|
|
|
+ <div class="pr-20">
|
|
|
+ <div class="text-base line-clamp-1">
|
|
|
+ Never gonna give you up
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-opacity-80 text-white">
|
|
|
+ Episode 1
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div ref="dragTarget" class="py-3">
|
|
|
+ <div
|
|
|
+ class="progress-bar h-[2px] bg-white bg-opacity-20 rounded"
|
|
|
+ ref="progressBar"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-if="loading"
|
|
|
+ class="loading bg-white h-[2px] rounded bg-opacity-80"
|
|
|
+ ></div>
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="progress bg-white h-[2px] rounded bg-opacity-80"
|
|
|
+ :style="{ width: progress + '%' }"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="dive-into h-[44px] flex items-center text-xs bg-opacity-20 bg-black px-4 rounded"
|
|
|
+ @click.stop="showEpisodes"
|
|
|
+ >
|
|
|
+ <div class="flex-1">Dive into the story · 66 Episodes</div>
|
|
|
+ <img
|
|
|
+ src="@/assets/icon_into_small.svg"
|
|
|
+ style="width: 10px; height: auto"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="absolute right-4 bottom-32 flex flex-col">
|
|
|
+ <div class="btn flex flex-col items-center justify-center">
|
|
|
+ <img class="w-8 h-8" src="@/assets/icon_save.svg" />
|
|
|
+ <div class="text-xs">Save</div>
|
|
|
+ </div>
|
|
|
+ <div class="btn flex flex-col items-center justify-center mt-4">
|
|
|
+ <img class="w-8 h-8" src="@/assets/icon_share.svg" />
|
|
|
+ <div class="text-xs">Share</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="absolute left-4 right-4 bottom-24 shadow"
|
|
|
+ v-if="draggingData.dragging"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="text-center text-xl font-bold mb-2 drop-shadow text-white text-opacity-50"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ class="inline-block text-center w-16 text-white text-opacity-90"
|
|
|
+ >{{ toDuration }}</span
|
|
|
+ >/
|
|
|
+ <span class="inline-block text-center w-16">
|
|
|
+ {{ formatDuration(draggingData.videoDuration) }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="progress-bar-large h-2 bg-white bg-opacity-20 rounded">
|
|
|
+ <div
|
|
|
+ class="progress bg-white h-2 rounded bg-opacity-80"
|
|
|
+ :style="{ width: draggingData.toProgress + '%' }"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
-<script setup lang="ts"></script>
|
|
|
-<style lang="less" scoped></style>
|
|
|
+<script setup lang="ts">
|
|
|
+import { Ref, computed, onMounted, reactive, ref, watch } from "vue";
|
|
|
+import { useDrag } from "@vueuse/gesture";
|
|
|
+import { useElementBounding } from "@vueuse/core";
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ active: Boolean,
|
|
|
+});
|
|
|
+
|
|
|
+const video: Ref<HTMLVideoElement | null> = ref(null);
|
|
|
+const progress = ref(20);
|
|
|
+const loading = ref(false);
|
|
|
+const dragTarget = ref();
|
|
|
+const isDragging = ref(false);
|
|
|
+const progressBar: Ref<HTMLElement | null> = ref(null);
|
|
|
+
|
|
|
+const draggingData = reactive({
|
|
|
+ barWidth: 0,
|
|
|
+ videoDuration: 0,
|
|
|
+ progress: 0,
|
|
|
+ toProgress: 0,
|
|
|
+ dragging: false,
|
|
|
+});
|
|
|
+function formatDuration(duration: number) {
|
|
|
+ const minutes = Math.floor(duration / 60)
|
|
|
+ .toString()
|
|
|
+ .padStart(2, "0");
|
|
|
+ const seconds = Math.floor(duration % 60)
|
|
|
+ .toString()
|
|
|
+ .padStart(2, "0");
|
|
|
+ return `${minutes}:${seconds}`;
|
|
|
+}
|
|
|
+const toDuration = computed(() => {
|
|
|
+ return formatDuration(
|
|
|
+ Math.floor((draggingData.toProgress * draggingData.videoDuration) / 100)
|
|
|
+ );
|
|
|
+});
|
|
|
+const dragHandler = ({ movement: [x, y], dragging }: any) => {
|
|
|
+ console.log(x, dragging);
|
|
|
+ if (dragging && !draggingData.dragging) {
|
|
|
+ draggingData.barWidth = useElementBounding(progressBar).width.value;
|
|
|
+ draggingData.videoDuration = 100;
|
|
|
+ draggingData.progress = progress.value;
|
|
|
+ draggingData.toProgress = progress.value;
|
|
|
+ draggingData.dragging = true;
|
|
|
+ }
|
|
|
+ draggingData.toProgress = Math.max(
|
|
|
+ 0,
|
|
|
+ Math.min(
|
|
|
+ 100,
|
|
|
+ draggingData.progress +
|
|
|
+ Math.floor((x / draggingData.barWidth) * 100)
|
|
|
+ )
|
|
|
+ );
|
|
|
+ if (!dragging) {
|
|
|
+ draggingData.dragging = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+useDrag(dragHandler, {
|
|
|
+ domTarget: dragTarget,
|
|
|
+});
|
|
|
+
|
|
|
+function onTimeUpdate() {
|
|
|
+ progress.value = (video.value!.currentTime! / video.value!.duration!) * 100;
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ if (props.active) {
|
|
|
+ video.value?.play();
|
|
|
+ }
|
|
|
+});
|
|
|
+watch(
|
|
|
+ () => props.active,
|
|
|
+ (active) => {
|
|
|
+ if (active) {
|
|
|
+ video.value?.play();
|
|
|
+ } else {
|
|
|
+ video.value?.pause();
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
+function showEpisodes() {
|
|
|
+ console.log("showEpisodes");
|
|
|
+}
|
|
|
+</script>
|
|
|
+<style lang="less" scoped>
|
|
|
+.mask-info {
|
|
|
+ .title-info {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ margin-bottom: env(safe-area-inset-bottom, 16px);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.progress-bar {
|
|
|
+ .loading {
|
|
|
+ margin: auto;
|
|
|
+ animation: loading 0.5s linear infinite;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes loading {
|
|
|
+ 0% {
|
|
|
+ width: 0;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.progress-bar-large {
|
|
|
+ .progress {
|
|
|
+ position: relative;
|
|
|
+ &::after {
|
|
|
+ content: "";
|
|
|
+ position: absolute;
|
|
|
+ width: 12px;
|
|
|
+ height: 12px;
|
|
|
+ right: -6px;
|
|
|
+ top: -2px;
|
|
|
+ background: white;
|
|
|
+ z-index: 1;
|
|
|
+ border-radius: 6px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|