文章目录
- 22-首页主体-补充-vue动画
- 23-首页主体-面板骨架效果
- 24-首页主体-组件数据懒加载
- 25-首页主体-热门品牌
22-首页主体-补充-vue动画
目标: 知道vue中如何使用动画,知道Transition组件使用。
当vue中,显示隐藏,创建移除,一个元素或者一个组件的时候,可以通过transition实现动画。
如果元素或组件离开,完成一个淡出效果:
<transition name="fade">
<p v-if="show">100</p>
</transition>
.fade-leave {
opacity: 1
}
.fade-leave-active {
transition: all 1s;
}
.fade-leave-to {
opcaity: 0
}
- 进入(显示,创建)
- v-enter 进入前 (vue3.0 v-enter-from)
- v-enter-active 进入中
- v-enter-to 进入后
- 离开(隐藏,移除)
- v-leave 进入前 (vue3.0 v-leave-from)
- v-leave-active 进入中
- v-leave-to 进入后
多个transition使用不同动画,可以添加nam属性,name属性的值替换v即可。
23-首页主体-面板骨架效果
目的: 加上面板的骨架加载效果
定义一个骨架布局组件:
src/views/home/components/home-skeleton.vue
<template>
<div class='home-skeleton'>
<div class="item" v-for="i in 4" :key="i" :style="{backgroundColor:bg}">
<XtxSkeleton bg="#e4e4e4" width="306px" height="306px" animated />
<XtxSkeleton bg="#e4e4e4" width="160px" height="24px" animated />
<XtxSkeleton bg="#e4e4e4" width="120px" height="24px" animated />
</div>
</div>
</template>
<script>
export default {
name: 'HomeSkeleton',
props: {
bg: {
type: String,
default: '#fff'
}
}
}
</script>
<style scoped lang='less'>
.home-skeleton {
width: 1240px;
height: 406px;
display: flex;
justify-content: space-between;
.item {
width: 306px;
.xtx-skeleton ~ .xtx-skeleton{
display: block;
margin: 16px auto 0;
}
}
}
</style>
在 home-hot
home-new
组件分别使用
<HomePanel title="人气推荐" sub-title="人气爆款 不容错过">
+ <div style="position: relative;height: 426px;">
+ <Transition name="fade">
+ <ul v-if="goods.length" ref="pannel" class="goods-list">
<li v-for="item in goods" :key="item.id">
<RouterLink to="/">
<img :src="item.picture" alt="">
<p class="name">{{item.title}}</p>
<p class="desc">{{item.alt}}</p>
</RouterLink>
</li>
</ul>
+ <HomeSkeleton v-else />
+ </Transition>
+ </div>
</HomePanel>
<template>
<HomePanel title="新鲜好物" sub-title="新鲜出炉 品质靠谱">
<template v-slot:right><XtxMore /></template>
+ <div style="position: relative;height: 406px;">
+ <Transition name="fade">
+ <ul v-if="goods.length" ref="pannel" class="goods-list">
<li v-for="item in goods" :key="item.id">
<RouterLink to="/">
<img :src="item.picture" alt="">
<p class="name">{{item.name}}</p>
<p class="price">¥{{item.price}}</p>
</RouterLink>
</li>
</ul>
+ <HomeSkeleton bg="#f0f9f4" v-else />
+ </Transition>
+ </div>
</HomePanel>
</template>
在 src/assets/styles/common.less
定义动画
.fade{
&-leave {
&-active {
position: absolute;
width: 100%;
transition: opacity .5s .2s;
z-index: 1;
}
&-to {
opacity: 0;
}
}
}
注意:
- 动画的父容器需要是定位,防止定位跑偏。
24-首页主体-组件数据懒加载
目的: 实现当组件进入可视区域在加载数据。
我们可以使用 @vueuse/core
中的 useIntersectionObserver
来实现监听进入可视区域行为,但是必须配合vue3.0的组合API的方式才能实现。
大致步骤:
- 理解
useIntersectionObserver
的使用,各个参数的含义 - 改造 home-new 组件成为数据懒加载,掌握
useIntersectionObserver
函数的用法 - 封装
useLazyData
函数,作为数据懒加载公用函数 - 把
home-new
和home-hot
改造成懒加载方式
落的代码:
1.先分析下这个useIntersectionObserver
函数:
// stop 是停止观察是否进入或移出可视区域的行为
const { stop } = useIntersectionObserver(
// target 是观察的目标dom容器,必须是dom容器,而且是vue3.0方式绑定的dom对象
target,
// isIntersecting 是否进入可视区域,true是进入 false是移出
// observerElement 被观察的dom
([{ isIntersecting }], observerElement) => {
// 在此处可根据isIntersecting来判断,然后做业务
},
)
2.开始改造 home-new 组件:src/views/home/components/home-new.vue
- 进入可视区后获取数据
<div ref="box" style="position: relative;height: 406px;">
// 省略。。。
<script>
import HomePanel from './home-panel'
import HomeSkeleton from './home-skeleton'
import { findNew } from '@/api/home'
import { ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'
export default {
name: 'HomeNew',
components: { HomePanel, HomeSkeleton },
setup () {
const goods = ref([])
const box = ref(null)
const { stop } = useIntersectionObserver(
box,
([{ isIntersecting }]) => {
if (isIntersecting) {
stop()
findNew().then(data => {
goods.value = data.result
})
}
}
)
return { goods, box }
}
}
</script>
3.由于首页面板数据加载都需要实现懒数据加载,所以封装一个钩子函数,得到数据。
src/hooks/index.js
// hooks 封装逻辑,提供响应式数据。
import { useIntersectionObserver } from '@vueuse/core'
import { ref } from 'vue'
// 数据懒加载函数
export const useLazyData = (apiFn) => {
// 需要
// 1. 被观察的对象
// 2. 不同的API函数
const target = ref(null)
const result = ref([])
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }], observerElement) => {
if (isIntersecting) {
stop()
// 调用API获取数据
apiFn().then(data => {
result.value = data.result
})
}
}
)
// 返回--->数据(dom,后台数据)
return { target, result }
}
4.再次改造 home-new
组件:src/views/home/components/home-new.vue
import { findNew } from '@/api/home'
+import { useLazyData } from '@/hooks'
export default {
name: 'HomeNew',
components: { HomePanel, HomeSkeleton },
setup () {
+ const { target, result } = useLazyData(findNew)
+ return { goods: result, target }
}
}
+ <div ref="target" style="position: relative;height: 426px;">
5.然后改造 home-hot
组件:src/views/home/components/home-hot.vue
+ <div ref="target" style="position: relative;height: 426px;">
import { findHot } from '@/api/home'
import HomePanel from './home-panel'
import HomeSkeleton from './home-skeleton'
+import { useLazyData } from '@/hooks'
export default {
name: 'HomeHot',
components: { HomePanel, HomeSkeleton },
setup () {
+ const { target, result } = useLazyData(findHot)
+ return { target, list: result }
}
}
25-首页主体-热门品牌
目的: 实现品牌的展示,和切换品牌效果。
基本步骤:
- 准备基础布局组件
- 获取数据实现渲染,完成切换效果
- 加上骨架效果和数据懒加载
落的代码:
.基础结构:
src/views/home/components/home-brand.vue`
<template>
<HomePanel title="热门品牌" sub-title="国际经典 品质保证">
<template v-slot:right>
<a href="javascript:;" class="iconfont icon-angle-left prev"></a>
<a href="javascript:;" class="iconfont icon-angle-right next"></a>
</template>
<div class="box" ref="box">
<ul class="list" >
<li v-for="i in 10" :key="i">
<RouterLink to="/">
<img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/brand_goods_1.jpg" alt="">
</RouterLink>
</li>
</ul>
</div>
</HomePanel>
</template>
<script>
import HomePanel from './home-panel'
export default {
name: 'HomeBrand',
components: { HomePanel }
}
</script>
<style scoped lang='less'>
.home-panel {
background:#f5f5f5
}
.iconfont {
width: 20px;
height: 20px;
background: #ccc;
color: #fff;
display: inline-block;
text-align: center;
margin-left: 5px;
background: @xtxColor;
&::before {
font-size: 12px;
position: relative;
top: -2px
}
&.disabled {
background: #ccc;
cursor: not-allowed;
}
}
.box {
display: flex;
width: 100%;
height: 345px;
overflow: hidden;
padding-bottom: 40px;
.list {
width: 200%;
display: flex;
transition: all 1s;
li {
margin-right: 10px;
width: 240px;
&:nth-child(5n) {
margin-right: 0;
}
img {
width: 240px;
height: 305px;
}
}
}
}
</style>
2.使用组件:src/views/home/index.vue
<!-- 人气推荐 -->
<HomeHot />
<!-- 热门品牌 -->
+ <HomeBrand />
+import HomeBrand from './components/home-brand'
export default {
name: 'xtx-home-page',
+ components: { HomeCategory, HomeBanner, HomeNew, HomeHot, HomeBrand }
}
2.获取数据和切换效果:
- 由于最后会使用到数据懒加载,那么我们也会使用组合API实现。
- 业务上,只有两页数据切换,0—>1 或者 1—>0 的方式。
<template>
<HomePanel title="热门品牌" sub-title="国际经典 品质保证">
<template v-slot:right>
<a @click="toggle(-1)" :class="{disabled:index===0}" href="javascript:;" class="iconfont icon-angle-left prev"></a>
<a @click="toggle(1)" :class="{disabled:index===1}" href="javascript:;" class="iconfont icon-angle-right next"></a>
</template>
<div class="box">
<ul v-if="brands.length" class="list" :style="{transform:`translateX(${-index*1240}px)`}">
<li v-for="item in brands" :key="item.id">
<RouterLink to="/">
<img :src="item.picture" alt="">
</RouterLink>
</li>
</ul>
</div>
</HomePanel>
</template>
<script>
import { ref } from 'vue'
import HomePanel from './home-panel'
import { findBrand } from '@/api/home'
import { useLazyData } from '@/hooks'
export default {
name: 'HomeBrand',
components: { HomePanel },
setup () {
// 获取数据
const brands = ref([])
findBrand(10).then(data => {
brands.value = data.result
})
// 切换效果,前提只有 0 1 两页
const index = ref(0)
// 1. 点击上一页
// 2. 点击下一页
const toggle = (step) => {
const newIndex = index.value + step
if (newIndex < 0 || newIndex > 1) return
index.value = newIndex
}
return { brands, toggle, index }
}
}
</script>
3.加上数据懒加载和骨架效果
<template>
<HomePanel title="热门品牌" sub-title="国际经典 品质保证">
<template v-slot:right>
<a @click="toggle(-1)" :class="{disabled:index===0}" href="javascript:;" class="iconfont icon-angle-left prev"></a>
<a @click="toggle(1)" :class="{disabled:index===1}" href="javascript:;" class="iconfont icon-angle-right next"></a>
</template>
+ <div ref="target" class="box">
+ <Transition name="fade">
+ <ul v-if="brands.length" class="list" :style="{transform:`translateX(${-index*1240}px)`}">
<li v-for="item in brands" :key="item.id">
<RouterLink to="/">
<img :src="item.picture" alt="">
</RouterLink>
</li>
</ul>
+ <div v-else class="skeleton">
+ <XtxSkeleton class="item" v-for="i in 5" :key="i" animated bg="#e4e4e4" width="240px" height="305px"/>
+ </div>
+ </Transition>
</div>
</HomePanel>
</template>
<script>
import { ref } from 'vue'
import HomePanel from './home-panel'
import { findBrand } from '@/api/home'
+import { useLazyData } from '@/hooks'
export default {
name: 'HomeBrand',
components: { HomePanel },
setup () {
// 获取数据
// const brands = ref([])
// findBrand(10).then(data => {
// brands.value = data.result
// })
+ // 注意:useLazyData需要的是API函数,如果遇到要传参的情况,自己写函数再函数中调用API
+ const { target, result } = useLazyData(() => findBrand(10))
// 切换效果,前提只有 0 1 两页
const index = ref(0)
// 1. 点击上一页
// 2. 点击下一页
const toggle = (step) => {
const newIndex = index.value + step
if (newIndex < 0 || newIndex > 1) return
index.value = newIndex
}
+ return { brands: result, toggle, index, target }
}
}
</script>
.skeleton {
width: 100%;
display: flex;
.item {
margin-right: 10px;
&:nth-child(5n) {
margin-right: 0;
}
}
}
总结: 注意下useLazyData传参的情况。