Taro+vue3 实现电影切换列表功能
作者:一只小阿乐
我们做类似于猫眼电影的小程序或者H5 的时候 我们会做到那种 左右滑动的电影列表,这种列表一般带有电影场次,我这个项目是基于Taro +vue3 +ts 来写的用的组件库也是京东的nut-ui以上的代码和组件也有的是我二次封装的组件,对vue3电影切换列表知识,感兴趣的朋友一起看看吧
1.需求
我们在做类似于猫眼电影的小程序或者H5 的时候 我们会做到那种 左右滑动的电影列表,这种列表一般带有电影场次
2.效果
3.说明
这种效果在淘票票 猫眼电影上 都有的 ,一般电影类型的H5 或者小程序 这个是都有的 第一是好看 第二是客观性比较好
4.代码
1.整体页面 <template> <div class="movie-container-index"> <Header></Header> <div class="swiper-main"> <image class="background-img-vague" :src="chooseMovice.posterUrl" style="height: 100%;"></image> <div class="wrap"> <MovieList :list="movieList" @onchangeMovie="MovieChange" :model-value="chooseMovice.movieId"></MovieList> </div> <div class="box"></div> </div> <div class="movie-detail"> <div class="name">{{ chooseMovice.movieName }}</div> <div class="center-detail"> <div>{{ chooseMovice.movieType }}</div> <div>{{ chooseMovice.duration }}</div> </div> <div class="cast">{{ chooseMovice.cast }}</div> </div> <div class="movie-time-container"> <Filter v-if="timeList.length" :data="timeList" @onChanged="onTimeChanged"></Filter> </div> <div class="movie-arrgement-container" v-if="arrangementList.length && !loading"> <Item v-for="(item, index) in arrangementList" :info="item" :key="index"></Item> </div> <template v-if="arrangementList.length === 0 && loading"> <div style="padding: 0 15px;"> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> <nut-skeleton :style="{ width: '100%', margin: '0.5rem 0' }" width="100%" height="15px" title animated row="1"> </nut-skeleton> </div> </template> <template v-if="arrangementList.length === 0 && !loading"> <nut-empty description="暂无场次"></nut-empty> </template> </div> </template> <script setup> import Header from './header.vue' import MovieList from './movie-list.vue' import Filter from './filter.vue' import { ref, reactive, onMounted } from 'vue' import Item from './item.vue' const chooseMovice = ref({ movieId: 12343, duration: 149, movieType: '剧情|历史|战争', cast: '吴京 易烊千玺 段奕宏 张涵予 朱亚文', posterUrl: "https://gw.alicdn.com/i1/O1CN01sSmj2b1daSm6IAUcs_!!6000000003752-0-alipicbeacon.jpg_480x480Q30s150.jpg", movieName: '长津湖之水门桥', }) const timeList = ref(["2024-01-09", "2024-01-10", "2024-01-13"]) const arrangementList = ref([]) const loading = ref(true) const movieList = ref([ { movieId: 12343, duration: 149, movieType: '剧情|历史|战争', cast: '吴京 易烊千玺 段奕宏 张涵予 朱亚文', movieName: '长津湖之水门桥', posterUrl: "https://gw.alicdn.com/i1/O1CN01sSmj2b1daSm6IAUcs_!!6000000003752-0-alipicbeacon.jpg_480x480Q30s150.jpg" }, { cast: "易烊千玺 田雨 陈哈琳 齐溪 公磊 许君聪 王宁 黄尧 巩金国", duration: 106, movieId: 147885, movieName: "奇迹·笨小孩", movieType: "剧情", posterUrl: "https://gw.alicdn.com/i1/O1CN013Ggc2s1Z8HwrwxAfn_!!6000000003149-0-alipicbeacon.jpg_480x480Q30s150.jpg" }, { cast: "张译,李晨,魏晨,曹炳琨,王骁,张子贤,杨新鸣", duration: 106, movieId: 147886, movieName: "三大队", movieType: "剧情,犯罪", posterUrl: "https://gw.alicdn.com/i4/O1CN01zQvWon1SuCCkUXTr8_!!6000000002306-0-alipicbeacon.jpg_480x480Q30s150.jpg" }, { cast: "岑珈其,梁朝伟,刘德华,蔡卓妍,任达华,陈家乐,白只,姜皓文,方中信,太保,钱嘉乐,周家怡", duration: 106, movieId: 147887, movieName: "金手指", movieType: "犯罪,剧情", posterUrl: "https://gw.alicdn.com/i4/O1CN01mdCwok1K4QV7FV8gv_!!6000000001110-0-alipicbeacon.jpg_480x480Q30s150.jpg" }, { cast: "李栋,大鹏,白客,庄达菲,王迅,孙艺洲,李乃文", duration: 106, movieId: 147888, movieName: "年会不能停!", movieType: "喜剧,剧情", posterUrl: "https://gw.alicdn.com/i1/O1CN01v6g8341QMBaLa2FhI_!!6000000001961-0-alipicbeacon.jpg_480x480Q30s150.jpg" }, { cast: "屈楚萧,张佳宁,傅菁,蒋昀霖,牛超,田壮壮,沙溢", duration: 106, movieId: 147889, movieName: "一闪一闪亮星星", movieType: "爱情,奇幻", posterUrl: "https://gw.alicdn.com/i4/O1CN01W8mzt61aa3k1Xtw3N_!!6000000003345-0-alipicbeacon.jpg_480x480Q30s150.jpg" }, { cast: "江户川柯南,高山南,山崎和佳奈,小山力也,林原惠美,置鲇龙太郎,三石琴乃,土师孝也,乃村健次,飞田展男,绪方贤一,松井菜樱子,岩居由希子,大谷育江,茶风林", duration: 106, movieId: 147890, movieName: "名侦探柯南:黑铁的鱼影", movieType: "动画,悬疑,动作", posterUrl: "https://gw.alicdn.com/i1/O1CN01U9wWR81pfku0aqB6O_!!6000000005388-0-alipicbeacon.jpg_480x480Q30s150.jpg" } ]) const MovieChange = (e) => { console.log(e); chooseMovice.value = movieList.value.filter((item) => item.movieId == e)[0] } onMounted(() => { arrangementList.value = [ { finishTime: "1704782520000", hallName: "7号激光杜比全景声厅", id: "1194219979173253121", pickUpPrice: 3380, price: 3500, showTime: "1704775500000" } ] loading.value = false }) </script> <style lang="scss"> .movie-container-index { display: flex; flex-direction: column; .movie-arregment-container {} .swiper-main { height: 324px; position: relative; overflow: hidden; width: 100%; .background-img-vague { position: absolute; left: 0; right: 0; width: 100%; height: 100%; filter: blur(15px); -webkit-filter: blur(15px); } } .movie-detail { display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: #fff; padding: 20px; .name { font-size: 30px; font-weight: 700; color: #15181d; } .center-detail { display: flex; align-items: center; color: #858a99; font-size: 24px; margin-top: 5px; } .cast { color: #858a99; font-size: 24px; margin-top: 5px; word-break: break-all; text-align: center; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } } .wrap { padding: 10px 0; } .box { width: 0; height: 0; border: 10rpx solid; border-color: transparent transparent #fff #fff; transform: rotate(135deg); position: absolute; bottom: -10rpx; left: 0; right: 0; margin: 0 auto; } } </style>
2.movie-list代码 <template> <div class="movie-list-component"> <div class="list-container"> <scroll-view id="contentScroll" class="scroll-view" :scroll-with-animation="true" :scroll-left="scrollLeft" :scroll-x="true"> <div class="movie-item seat"></div> <div :data-id="item.movieId" @click="selectMovie" :id="`movieItem${item.movieId}`" class="movie-item" :class="{ active: modelValue == item.movieId }" v-for="( item, index ) in list " :key="index"> <div class="img-container"> <image class="img" :src="item?.posterUrl" alt="" /> </div> </div> <div class="movie-item seat1"></div> </scroll-view> </div> </div> </template> <script setup lang="ts"> import Taro from "@tarojs/taro"; import { onMounted, ref, reactive, toRefs, watch } from "vue"; const props = defineProps({ // 子组件接收父组件传递过来的值 list: { type: Array<any>, required: true, }, modelValue: { type: Number, required: true, }, }); //使用父组件传递过来的值 const { list, modelValue } = toRefs(props); const emit = defineEmits(["onchangeMovie"]); onMounted(() => { // selectMovie(list.value[0].movieId) list?.value.map((item, index) => { if (item.id === modelValue?.value) { list?.value.unshift(list?.value.splice(index, 1)[0]); } }); }); const scrollLeft = ref(0); //选择电影 const selectMovie = (e) => { let offsetLeft = e.currentTarget.offsetLeft; let { id } = e.currentTarget.dataset; if (!id) { id = list.value[0].movieId; } if (modelValue.value == id) { return; } emit("onchangeMovie", id); getRect(id, offsetLeft); }; //计算电影item的偏移量 const getRect = async (id, offsetLeft) => { const eleId = `#movieItem${id}`; const contentScrollWidth: any = await getContentScrollWidth("#contentScroll"); const query = Taro.createSelectorQuery(); query.select(eleId).boundingClientRect(); query.selectViewport().scrollOffset(); query.exec(async (res) => { //获取item的宽度de 一半 const subhalfwidth = res[0].width / 2; //需要scrollview 移动的距离是 const juli = offsetLeft - contentScrollWidth / 2 + subhalfwidth; scrollLeft.value = juli; }); }; // 获取ScrollView的宽度 const getContentScrollWidth = (ele) => { return new Promise((resolve) => { const query = Taro.createSelectorQuery(); query.select(ele).boundingClientRect(); query.selectViewport().scrollOffset(); query.exec((res) => { const width = res[0].width; resolve(width); }); }); }; </script> <style lang="scss"> .movie-list-component { display: flex; flex-direction: column; .list-container { display: flex; flex-direction: column; justify-content: center; height: 324px; .scroll-view { width: 100%; height: 324px; white-space: nowrap; position: relative; .movie-item:nth-child(n+2) { margin-left: 35px; } .movie-item { display: inline-block; position: relative; margin-top: 30px; border-radius: 18px; width: 156px; height: 218px; // line-height: 208px; transition: width 1s; transition: height 1s; .img-container { border-radius: 8px; width: 100%; height: 100%; overflow: hidden; position: relative; z-index: 2; // border: 5px #ffffff solid; .img { width: 100%; height: 100%; } } } .movie-item.active { transform: scale(1.15); /* 放大1.2倍 */ transition: transform 1s ease; /* 过渡效果 */ } .seat { display: inline-block; width: 50%; // height: 290px; margin-left: -110px; } .seat1 { display: inline-block; width: 35%; } } } } </style>
filter 组件 <template> <div class="cinema-detail-filter-container"> <div class="date-tab"> <nut-tabs :title-gutter="15" v-model="tabTimesIndex" title-scroll> <nut-tab-pane v-for="(item, index) in tabTimesList" :pane-key="index" :title="`${item.t} ${item.a}`"> </nut-tab-pane> </nut-tabs> </div> </div> </template> <script setup lang="ts"> import { computed, onMounted, ref, toRefs, watch } from 'vue'; import moment from "moment"; const tabTimesIndex = ref(0) const tabTimesList: any = ref([]) const props = defineProps({ //子组件接收父组件传递过来的值 data: Object, }); //使用父组件传递过来的值 const { data: timesList } = toRefs(props); onMounted(() => { getTimes() }); const emit = defineEmits(['onChanged']) watch(tabTimesIndex, (index) => { emit('onChanged', tabTimesList.value[index]) }) watch(tabTimesList, () => { emit('onChanged', tabTimesList.value[0]) }) const getTimes = () => { const times = timesList?.value const arr: any = [] times?.forEach(item => { const time = item let t = getweek(moment(time).startOf('day').format('E')) if (time === moment().format('YYYY-MM-DD')) { t = '今天' } else if (time === moment().subtract(-1, 'days').format('YYYY-MM-DD')) { t = '明天' } else if (time === moment().subtract(-2, 'days').format('YYYY-MM-DD')) { t = '后天' } arr.push({ time: time, t: t, a: time.substr(5, 9) }) }); tabTimesList.value = arr } const getweek = (val) => { const week = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] return week[val - 1]; }; </script> <style lang="scss"> .cinema-detail-filter-container { display: flex; flex-direction: column; .nut-tabs { .nut-tabs__titles { background: #ffffff !important; .nut-tabs__titles-item { .nut-tabs__titles-item__smile { display: none; } .nut-tabs__titles-item__text { color: #858a99; font-size: 22px !important; } } .nut-tabs__titles-item__line { background: linear-gradient(to right, #5232B7, #7237BC, #5232B7) !important; border-radius: 30px !important; } .nut-tabs__titles-item.active { .nut-tabs__titles-item__smile { display: block; margin-top: 10px; } .nut-tabs__titles-item__text { color: #15181d; } } } .nut-tabs__content { display: none !important; } } } </style>
header 组件 <template> <div class="movie-header-box"> <div class="left"> <div class="name">万达影城(北京昌平保利光魔店)</div> <div class="address">昌平区鼓楼街贾琏时代广场四楼</div> </div> <div class="right"> <IconFont name="locationg3" color="#5232B7"></IconFont> </div> </div> </template> <script setup> import { IconFont } from '@nutui/icons-vue-taro'; </script> <style lang="scss"> .movie-header-box { display: flex; align-items: center; justify-content: space-between; background-color: #fff; padding: 25px 30px; .left { .name { color: #15181d; font-weight: 700; font-size: 26px; } .address { color: #858a99; font-size: 22px; margin-top: 10px; } } } </style>
5.我这个项目是基于Taro +vue3 +ts 来写的 用的组件库也是京东的nut-ui 以上的代码和组件 也有的是我二次封装的组件 组件也挺方便的
到此这篇关于Taro+vue3 实现电影切换列表的文章就介绍到这了,更多相关vue3电影切换列表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!