本文主要讲解关于vue3 + css3 实现动效overview页面相关内容,让我们来一起学习下吧!
一、要点
1. 保持背景视频仅铺满第一屏并适配不同尺寸显示器。【🌟】
-
- 通过媒体查询适配宽高比例,控制width跟height的100vh/vw切换。
-
- 页面挂载完毕时执行视频自动静音播放(非静音无法静默播放)。
2. 导航栏动态吸顶。【🌟】
-
- 监听页面滚动距离,超出一屏时切换isFixed改变导航栏的position定位。
-
- 加入animation动画实现缓入缓出(此处不宜使用transition)。
-
- 结合window.scrollTo实现导航栏锚点缓滚。
3. 跑马灯logo列表横向滚动。【🌟🌟】
-
- 加入animation动画实现横向滚动。
-
- 结合css变量设置父子宽度实现流畅持续滚动。
4. 内容根据滚动情况动态展示。【🌟】
-
- 监听页面滚动距离,指定距离触发动态展示。
-
- 为需要动态展示的标签添加标识类名。
-
- 结合animation,delay,transform实现动画延时执行。
5. 按钮默认炫光跑马特效,hover时光源扩散效果实现。【🌟🌟🌟】
-
- 通过定时器状态1实现默认炫光跑马特效。
-
- 通过定时器状态2停止定时器状态1和状态3,实现mouseenter时光源扩散。
-
- 通过定时器状态3停止定时器状态1和状态2,实现mouseleave时光源聚集。
6. banner切换通过元素组合动态实现。【🌟🌟🌟】
-
- 还原每个banner元素的位置及大小。
-
- 设置对应每个bannerItem需要展示的元素及元素的变形/位移效果。
-
- 通过mouseenter实现bannerItem的切换及动画转场。
二、6个要点实现详情
1. 保持背景视频仅铺满第一屏并适配不同尺寸显示器。
<div class="leocomedy-banner" @mouseenter="bannerPlayVideoHandle">
<video class="leocomedy-banner__video"
ref="bannerVideoPlayer"
:src="bannerVideoSrc"
autoplay
loop
preload="auto"
playsinline
></video>
</div>
-
- 通过媒体查询适配宽高比例,控制width跟height的100vh/vw切换。
@media screen and (max-aspect-ratio: 1920/1080) {
.leocomedy-banner__video {
height: 100vh;
width: auto;
}
}
-
- 页面挂载完毕时执行视频自动静音播放(非静音无法静默播放)。
const bannerVideoPlayer = ref(null);
const bannerVideoSrc = ref(leocomedyVideo);
const bannerPlayVideoHandle = () => {
if (bannerVideoSrc) {
bannerVideoPlayer.value.muted = true;
bannerVideoPlayer.value.play();
}
};
onMounted(() => {
bannerPlayVideoHandle()
})
})
完整代码
2. 导航栏动态吸顶。
<Nav :isFixed="animationData.navFixed"></Nav>
-
- 监听页面滚动距离,超出一屏时切换isFixed改变导航栏的position定位。
const animationData = reactive({
navFixed: false,
navFixedZone: null
})
onMounted(() => {
nextTick(() => {
let top = 0,
windowHeight = window.innerHeight;
const navEl = document.getElementsByClassName('leocomedy-nav');
window.addEventListener('scroll', () => {
top = window.pageYOffset;
if (top <= windowHeight) {
if ((navEl[0].classList[1] === 'isFixed') && !animationData.navFixedZone) {
navEl[0].classList.add('isNavTransition');
animationData.navFixedZone = setTimeout(() => {
animationData.navFixed = false;
animationData.navFixedZone = null;
}, 300);
}
} else {
animationData.navFixed = true;
}
})
})
})
-
- 加入animation动画实现缓入缓出(此处不宜使用transition,否则宽度变化及定位变化会导致标签变化怪异,非预期效果)。
.isFixed {
position: fixed;
z-index: 3;
color: #FFFFFF;
margin: 20px auto 0;
width: calc(100% - 240px);
border-radius: 999px;
animation: isFixedShow 0.5s forwards 1;
backdrop-filter: blur(8px);
background-color: #61616133;
border: 3px solid #5e5e5e4d;
height: 76px;
left: 0;
right: 0;
}
@keyframes isFixedShow {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
-
- 结合window.scrollTo实现导航栏锚点缓滚。
const navItemHandle = (item) => {
if (item.site) {
const getEl = document.getElementById(item.site);
window.scrollTo({
top: getEl.offsetTop - 84,
behavior: 'smooth'
})
}
}
完整代码
3. 跑马灯logo列表横向滚动。
<div class="leocomedy-linescroll"
:style="[
{'--linAllWidth': linAllWidth},
{'--linOnceWidth': linOnceWidth}
]"
>
<div class="leocomedy-linescroll-box">
<div class="leocomedy-linescroll-double"
v-for="item in 4"
>
<div class="leocomedy-linescroll-item"
v-for="(item, index) in baseData.lineList" :key="index"
>
<img class="leocomedy-linescroll-item__img" :src="item" />
</div>
</div>
</div>
</div>
-
- 加入animation动画实现横向滚动。
animation: scrollToLeft 20s linear infinite;
@-webkit-keyframes scrollToLeft {
from {
-webkit-transform: translateX(0px);
transform: translateX(0px);
}
to {
-webkit-transform: translateX(calc(0px - var(--linAllWidth) + 2 * var(--linOnceWidth)));
transform: translateX(calc(0px - var(--linAllWidth) + 2 * var(--linOnceWidth)));
}
}
@keyframes scrollToLeft {
from {
-webkit-transform: translateX(0px);
transform: translateX(0px);
}
to {
-webkit-transform: translateX(calc(0px - var(--linAllWidth) + 2 * var(--linOnceWidth)));
transform: translateX(calc(0px - var(--linAllWidth) + 2 * var(--linOnceWidth)));
}
}
-
- 结合css变量设置父子宽度实现流畅持续滚动。
const linAllWidth = ref('0px')
const linOnceWidth = ref('0px')
onMounted(() => {
linOnceWidth.value = ((184.2 * baseData.lineList.length) + 'px');
linAllWidth.value = (4 * (184.2 * baseData.lineList.length) + 'px');
})
完整代码
4. 内容根据滚动情况动态展示。
<div class="leocomedy-banner-title animationBox"
:style="{
'animation-delay' : 0.2 + 's'
}"
>
<h1 class="leocomedy-banner-l__h1">先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。</h1>
</div>
-
- 监听页面滚动距离,指定距离触发动态展示。
// 获取元素距离页面顶部的距离
const getElementTop = (element) => {
const rect = element.getBoundingClientRect();
return rect.top + window.scrollY; // 加上scrollY因为top是相对于视窗的
}
// 设置每个元素执行动画的延时
const settimeAnimation = (animationBoxsItem) => {
animationBoxsItem.classList.add("animationAction");
}
// 遍历页面上的animationBox,根据滚动及元素距离页面顶部距离判断是否要执行动画
const controllAnimation = (nowtop = 0, windowHeight) => {
const animationBoxs = document.getElementsByClassName('animationBox');
if (animationBoxs.length <= 0) return;
for (let i = 0; i < animationBoxs.length; i++) {
if (nowtop !== getElementTop(animationBoxs[i])) {
if (getElementTop(animationBoxs[i]) - nowtop - (windowHeight * (10/10)) <= 0) {
settimeAnimation(animationBoxs[i])
}
}
}
}
onMounted(() => {
nextTick(() => {
let top = 0,
windowHeight = window.innerHeight;
const navEl = document.getElementsByClassName('leocomedy-nav');
window.addEventListener('scroll', () => {
top = window.pageYOffset;
controllAnimation(top, windowHeight);
})
controllAnimation(top, windowHeight);
})
})
-
- 为需要动态展示的标签添加标识类名。
.animationBox {
opacity: 0;
transform: translateY(30px);
}
.animationAction {
animation:animationShow 0.5s forwards 1;
}
@keyframes animationShow {
0% {
transform: translateY(30px);
opacity: 0;
}
100% {
transform: translateY(0px);
opacity: 1;
}
}
-
- 结合animation,delay,transform实现动画延时执行。
animation-delay
5. 按钮默认炫光跑马特效,hover时光源扩散效果实现。
<div class="leocomedy-btn"
@click="routerPush"
@mouseenter="btnMouse(true)"
@mouseleave="btnMouse(false)"
:style="{'--eightGradient': baseData.eightGradient}"
>
<div class="leocomedy-btn-con">
<span class="leocomedy-btn-txt">{{ btnText }}</span>
<i class="icon-arrow"></i>
</div>
<div class="leocomedy-btn-bg"
:style="{ '--transform': baseData.transform }"
></div>
</div>
<!-- base -- />
const baseData = reactive({
transformNum: 0,
transform: "rotate(0deg) translate(-50%,-50%)",
timeZone: null,
transformScalePaused: 1,
transformNumPaused: 50,
timeZonePaused: null,
eightX: 50,
eightY: 100,
eightGradient: "radial-gradient(25% 50% at 50% 0%, #c8aefc 0%, rgba(104, 39, 230, 0.9) 50%, rgba(103, 39, 230, 0.32) 100%)"
})
.leocomedy-btn {
box-sizing: border-box;
width: auto;
height: auto;
display: inline-flex;
flex-direction: row;
justify-content: center;
align-items: center;
overflow: visible;
align-content: center;
flex-wrap: nowrap;
position: relative;
cursor: pointer;
.leocomedy-btn-con {
width: 100%;
height: 100%;
background: #000000;
padding: 20px 28px 20px 28px;
border-radius: 118px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
position: relative;
z-index: 1;
.leocomedy-btn-txt {
color: #ffffff;
font-weight: bold;
font-size: 15px;
line-height: 1.2;
}
}
.leocomedy-btn-bg {
display: block;
position: absolute;
width: calc(100% + 4px);
height: calc(100% + 4px);
z-index: 0;
border-radius: 61px;
overflow: hidden;
&:after{
display: block;
content: "";
height: 0;
padding-bottom: calc(100% + 20px);
width: calc(100% + 20px);
background: radial-gradient(28.8937% 26.4759% at 69.3682% 74.4384%, #A578FF 0%, #6727e6 68.7688%, rgba(103, 39, 230, 0.24) 100%)calc(100% + 20px);
border-radius: 180px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%) scale(1);
transform-origin: 0 0;
transform: var(--transform);
}
}
&::after {
content: "";
position: absolute;
width: 167px;
height: 55px;
filter: blur(15px);
background: var(--eightGradient);
overflow: hidden;
border-radius: 72px;
z-index: 0;
}
.icon-arrow {
margin-left: 8px;
width: 18px;
height: 18px;
background-color: #FFFFFF;
-webkit-mask: url(@/assets/leocomedybtnArrow.png) no-repeat;
mask: url(@/assets/leocomedybtnArrow.png) no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
}
-
- 通过定时器状态1实现默认炫光跑马特效。
// 旋转事件
const countDown = () => {
if (baseData.timeZonePaused) {
restoreHandle()
return
}
baseData.timeZone = setInterval(() => {
if (baseData.transformNum < 360) {
if (((baseData.transformNum <= 120) && (baseData.transformNum > 75)) || ((baseData.transformNum <= 300) && (baseData.transformNum > 255))) {
// 半圆,基于π,但忘记是怎么推导出0.349了
baseData.transformNum += 0.349
} else {
baseData.transformNum += 1
}
} else {
baseData.transformNum = 0
}
baseData.transform = `rotate(${baseData.transformNum}deg) translate(-50%,-50%) scale(1)`
}, 8.333)
// 3s
}
-
- 通过定时器状态2停止定时器状态1和状态3,实现mouseenter时光源扩散。
// 停止旋转后执行放大事件
const pausedHandle = () => {
if (baseData.timeZone) {
clearInterval(baseData.timeZone)
baseData.timeZone = null
}
clearInterval(baseData.timeZonePaused)
baseData.timeZonePaused = null
baseData.timeZonePaused = setInterval(() => {
if (baseData.transformScalePaused < 4) {
baseData.transformScalePaused += 0.0166
baseData.transformNumPaused += 1.388
} else {
clearInterval(baseData.timeZonePaused)
}
baseData.transform = `rotate(${baseData.transformNum}deg) translate(-${baseData.transformNumPaused}%,-${baseData.transformNumPaused}%) scale(${baseData.transformScalePaused})`
}, 8.333)
// 1.5s
}
-
- 通过定时器状态3停止定时器状态1和状态2,实现mouseleave时光源聚集。
// 停止放大后执行缩小事件
const restoreHandle = () => {
clearInterval(baseData.timeZonePaused)
baseData.timeZonePaused = null
baseData.timeZonePaused = setInterval(() => {
if (baseData.transformScalePaused > 1) {
baseData.transformScalePaused -= 0.0166
baseData.transformNumPaused -= 1.388
} else {
clearInterval(baseData.timeZonePaused)
baseData.timeZonePaused = null
countDown()
}
baseData.transform = `rotate(${baseData.transformNum}deg) translate(-${baseData.transformNumPaused}%,-${baseData.transformNumPaused}%) scale(${baseData.transformScalePaused})`
}, 8.333)
// 1.5s
}
完整代码
6. banner切换通过元素组合动态实现。
-
- 还原每个banner元素的位置及大小。
<div class="skeleton-box" :class="[baseData.videoSkeleton]">
<img :src="skeleton101" class="skeleton101 skeleton-box-img" />
<img :src="skeleton102" class="skeleton102 skeleton-box-img" />
<img :src="skeleton103" class="skeleton103 skeleton-box-img" />
<img :src="skeleton202" class="skeleton202 skeleton-box-img" />
<img :src="skeleton301" class="skeleton301 skeleton-box-img" />
</div>
-
- 设置对应每个bannerItem需要展示的元素及元素的变形/位移效果。
.skeleton103 {
animation: skleton103SkewHide 0.75s forwards 1, skleton103OpacityHide 0.75s forwards 0.75s 1;
}
@keyframes skleton103SkewHide {
0% {
transform: skew(4deg, -2deg) rotate(-1deg) scale(0.585, 0.585);
right: 79px;
bottom: -47px;
opacity: 1;
}
100% {
transform: skew(0deg, 0deg) rotate(0deg) scale(1, 1);
right: 0px;
bottom: 0px;
opacity: 1;
}
}
@keyframes skleton103OpacityHide {
0% {
transform: skew(0deg, 0deg) rotate(0deg) scale(1, 1);
right: 0px;
bottom: 0px;
opacity: 1;
}
100% {
transform: skew(0deg, 0deg) rotate(0deg) scale(1, 1);
right: 0px;
bottom: 0px;
opacity: 0;
}
}
-
- 通过mouseenter实现bannerItem的切换及动画转场。
const radiusItemMouse = (index) => {
if (index === baseData.default) return;
baseData.videoSkeleton = "skeleton-box-";
if (baseData.default === 0) {
baseData.videoSkeleton += (index === 1) ? '102' : '103';
}
if (baseData.default === 1) {
baseData.videoSkeleton += (index === 0) ? '201' : '203';
}
if (baseData.default === 2) {
baseData.videoSkeleton += (index === 0) ? '301' : '302';
}
baseData.default = index;
}
以上就是关于vue3 + css3 实现动效overview页面相关的全部内容,希望对你有帮助。欢迎持续关注程序员导航网,学习愉快哦!
暂无评论...