B站的前端样式一直是我学习和模仿的对象,特别是它的首页头部动画,可以随着鼠标的移动进行场景的变化,很酷,所以我对它进行了大体上的模仿
废话不多说,先看效果
仿B站首页动画演示
视频打不开点击这里
完整代码如下:这里将css、js、html代码整合到一块去了,大家也可以去码云自行下载
码云地址
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.bili-banner {
margin: 0 auto;
position: relative;
z-index: 0;
min-height: 155px;
height: 9.375vw;
min-width: 999px;
background-color: pink;
display: flex;
justify-content: center;
background-repeat: no-repeat;
background-position: center 0;
background-size: cover;
}
.animateds-banner {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
}
.animateds-banner > .layer {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<!--
1.多图层组合的静态页面
2.不同图层具有不同的清晰度
第六张图片:从初始位置鼠标右移,opacity从0增加(不一定到1,这与初始位置在屏幕的位置有关),左移保持0不变
第九、十张图片:从初始位置鼠标左移移,opacity从0增加(不一定到1,这与初始位置在屏幕的位置有关),右移保持0不变
左移offset大于0,右移offset小于0
3.鼠标移动导致清晰度变化(渐变效果)
4.鼠标移动导致左右跟随
5.鼠标离开,效果还原
-->
<div class="bili-banner">
<div class="animateds-banner">
<div class="layer">
<img src="img/1.png" height="184" data-height="360" data-width="9666" width="4957" />
</div>
<div class="layer">
<img src="img/2.png" height="184" data-height="360" data-width="9666" width="4957" />
</div>
<div class="layer">
<img src="img/3.png" height="166" data-height="360" data-width="3523" width="1626" />
</div>
<div class="layer">
<img src="img/4.png" height="180" data-height="360" data-width="2938" width="1476" />
</div>
<div class="layer">
<img src="img/5.png" height="64" data-height="139" data-width="556" width="256" />
</div>
<div class="layer">
<img src="img/6.png" height="86" data-height="302" data-width="734" width="210" />
</div>
<div class="layer">
<img src="img/7.png" height="129" data-height="180" data-width="1757" width="1261" />
</div>
<div class="layer">
<img src="img/8.png" height="83" data-height="116" data-width="1757" width="1261" />
</div>
<div class="layer">
<img src="img/9.png" height="141" data-height="346" data-width="497" width="203" />
</div>
<div class="layer">
<img src="img/10.png" height="105" data-height="256" data-width="146" width="59" />
</div>
<div class="layer">
<img src="img/11.png" height="117" data-height="254" data-width="602" width="277" />
</div>
<div class="layer">
<img src="img/12.png" height="184" data-height="360" data-width="4277" width="2193" />
</div>
<div class="layer">
<img src="img/13.png" height="150" data-height="327" data-width="933" width="430" />
</div>
<div class="layer">
<img src="img/14.png" height="217" data-height="353" data-width="740" width="455" />
</div>
<div class="layer">
<img src="img/15.png" height="184" data-height="360" data-width="1916" width="982" />
</div>
<canvas width="1677" height="159" style="position: absolute; top: 0px; left: 0px; "></canvas>
</div>
</div>
<script>
let banner = document.querySelector('.animateds-banner')
// 获取到所有的图层,也就是包裹在img外的所有div
let layers = document.querySelectorAll('.layer')
let layers_data = [
{ translateX: 0, translateY: -15.3871, filter_blur: 0, opacity: 1},
{ translateX: 1128.39, translateY: 0, filter_blur: 0, opacity: 1},
{ translateX: 692.419, translateY: 0, filter_blur: 0, opacity: 1},
{ translateX: -653.439, translateY: 0, filter_blur: 0, opacity: 1},
{ translateX: 623.177, translateY: 46.1613, filter_blur: 0, opacity: 1},
{ translateX: 258.503, translateY: 37.3394, filter_blur: 0, opacity: 0},
{ translateX: 114.89, translateY: 14.3613, filter_blur: 0, opacity: 1},
{ translateX: -359.032, translateY: 50.2645, filter_blur: 0, opacity: 1},
{ translateX: -246.194, translateY: 16.4129, filter_blur: 0, opacity: 0},
{ translateX: -348.774, translateY: 32.8258, filter_blur: 0, opacity: 0},
{ translateX: -92.3226, translateY: 13.8484, filter_blur: 0, opacity: 1},
{ translateX: 102.581, translateY: 0, filter_blur: 0, opacity: 1},
{ translateX: 221.574, translateY: 13.8484, filter_blur: 0, opacity: 1},
{ translateX: 2154.19, translateY: 0, filter_blur: 2, opacity: 1},
{ translateX: -1025.81, translateY: 0, filter_blur: 1, opacity: 1},
]
// 初始化所有的图层
let init = function() {
for (const k in layers) {
if (Object.hasOwnProperty.call(layers, k)) {
// 获取到当前图层(div),其中k是索引
// console.log(k);
const element = layers[k]
// 获取到当前图层所需要的数据
let element_data = layers_data[k]
element.children[0].style = 'transform: scale(1) translate('+ element_data.translateX +'px,'+ element_data.translateY +'px) rotate(0deg); filter: blur('+ element_data.filter_blur +'px); opacity:'+ element_data.opacity +';'
// 第二种方式:模板字符串
// element.children[0].style = `transform: scale(1)
// translate( ${element_data.translateX}px, ${element_data.translateY}px )
// rotate(0deg);
// filter: blur(${element_data.filter_blur});
// opcatity: ${element_data.opacity};
// `
}
}
}
let x_first = 0
let x_now = 0
let x_offset = 0
// 鼠标悬浮,记录鼠标第一次到banner的位置x_first
banner.addEventListener("mouseover", function(e) {
x_first = e.clientX
// console.log(x_first);
})
// 鼠标移动
banner.addEventListener("mousemove", function(e) {
// 鼠标移动的当前位置
x_now = e.clientX
// 在观察B站的规律后,发现:
// 鼠标移动到该banner的位置,此处为初始位置,在接下来的鼠标移动位置过程中,translate的值的改变都是基于这个点的,
// 而x_offset = x_first - x_now正好将这个问题包裹进去,x_first就是该初始位置,translate的值也就是根据x_offset来变的
x_offset = x_first - x_now
// 每个图片都要根据x_offset移动响应的距离
for (const k in layers) {
if (Object.hasOwnProperty.call(layers, k)) {
let level = (15 - parseInt(k)) * 0.5;
// 获取当前图层
const element = layers[k]
// 获取初始化图层数据,以便在此基础上进行改变
const element_data = layers_data[k]
let new_translateX = element_data.translateX + x_offset / level
let new_opacity = element_data.opacity
if (k == 5 && x_offset < 0) {
new_opacity = 0.333
} else if ((k == 8 || k == 9) && x_offset > 0) {
new_opacity = 0.44
} else {
new_opacity = element_data.opacity
}
element.children[0].style = `transform: scale(1)
translate( ${new_translateX}px, ${element_data.translateY}px )
rotate(0deg);
filter: blur(${element_data.filter_blur}px);
opacity: ${new_opacity};
`
}
}
})
// 鼠标移出,恢复到默认位置
banner.addEventListener("mouseout", function() {
init()
})
window.onload = function() {
init()
}
</script>
</body>
</html>
我就在这里讲讲我的思路吧,因为效果也不是完全一致的,就说一下在写代码过程中遇到的一些问题
- 首先,我先看的B站源代码,没有找到控制该效果的js文件,所以就先将其html框架copy下来了,当然,它的样式是这样的
我的代码在它的基础上将img标签中的内联样式进行了提取,至于为什么要这样做,一会会说明,css样式是一样的,就直接copy引入即可
- 去B站首页观察这个效果和它的源代码的时候,我发现了以下几点:首先,这一张一张的图片的移动速度肯定是不相同的,第二,在我鼠标移动进去这个区域的时候,图片的translate的值是不变的,也就是说,鼠标第一次进入这个区域的时候,这个位置就是移动的初始位置,移动过程中,改变相应速率的translate的值,第三,鼠标移出以后,恢复到原来的状态
- 有了以上信息,就开始着手做了,首先,为了将所有的图片组合成一个静态页面,就免不了对图片的tanslate的值进行相应的改变,有的图片改变X,有的图片同时改变X和Y,而鼠标移动的过程,只需要改变translateX的值就行,所以将img的内联样式提取出来成为一个对象数组,这样读取很简单,因为初始化的数据只在一开始呈现页面时使用
let layers_data = [
{ translateX: 0, translateY: -15.3871, filter_blur: 0, opacity: 1},
{ translateX: 1128.39, translateY: 0, filter_blur: 0, opacity: 1},
{ translateX: 692.419, translateY: 0, filter_blur: 0, opacity: 1},
{ translateX: -653.439, translateY: 0, filter_blur: 0, opacity: 1},
{ translateX: 623.177, translateY: 46.1613, filter_blur: 0, opacity: 1},
{ translateX: 258.503, translateY: 37.3394, filter_blur: 0, opacity: 0},
{ translateX: 114.89, translateY: 14.3613, filter_blur: 0, opacity: 1},
{ translateX: -359.032, translateY: 50.2645, filter_blur: 0, opacity: 1},
{ translateX: -246.194, translateY: 16.4129, filter_blur: 0, opacity: 0},
{ translateX: -348.774, translateY: 32.8258, filter_blur: 0, opacity: 0},
{ translateX: -92.3226, translateY: 13.8484, filter_blur: 0, opacity: 1},
{ translateX: 102.581, translateY: 0, filter_blur: 0, opacity: 1},
{ translateX: 221.574, translateY: 13.8484, filter_blur: 0, opacity: 1},
{ translateX: 2154.19, translateY: 0, filter_blur: 2, opacity: 1},
{ translateX: -1025.81, translateY: 0, filter_blur: 1, opacity: 1},
]
4.给每一个图层,也就是每个img标签赋值(for in循环)就可以得到所有图层结合的静态页面,我将这个过程封装到了初始化函数中,因为鼠标移开效果复原也是相同的操作,所以直接调用init函数即可,同时注意在给样式重新赋值的时候,有两种写法,我个人比较喜欢模板字符串,支持换行,然后数据拼接也比较简洁易懂,鼠标移出,恢复原状,那么也调用一次init函数
// 初始化所有的图层
let init = function() {
for (const k in layers) {
if (Object.hasOwnProperty.call(layers, k)) {
// 获取到当前图层(div),其中k是索引
// console.log(k);
const element = layers[k]
// 获取到当前图层所需要的数据
let element_data = layers_data[k]
element.children[0].style = 'transform: scale(1) translate('+ element_data.translateX +'px,'+ element_data.translateY +'px) rotate(0deg); filter: blur('+ element_data.filter_blur +'px); opacity:'+ element_data.opacity +';'
// 第二种方式:模板字符串
// element.children[0].style = `transform: scale(1)
// translate( ${element_data.translateX}px, ${element_data.translateY}px )
// rotate(0deg);
// filter: blur(${element_data.filter_blur});
// opcatity: ${element_data.opacity};
// `
}
}
}
// 鼠标移出,恢复到默认位置
banner.addEventListener("mouseout", function() {
init()
})
5.静态页面弄好了以后,就可以给不同的图层赋值不同的移动速率了,鼠标移动的这个操作分为两个过程,第一,鼠标首次到达该区域,第二,鼠标移动。
鼠标刚到该区域的时候,是初始化的位置,无需任何操作,只需记录下鼠标的初始位置x_first即可,后面的操作根据这个初始位置进行translateX的动态赋值
// 鼠标悬浮,记录鼠标第一次到banner的位置x_first
banner.addEventListener("mouseover", function(e) {
x_first = e.clientX
// console.log(x_first);
})
鼠标移动过程中,x_new记录当前鼠标的位置,x_offset = x_first - x_new代表鼠标移动的位移,左移offset大于0,右移offset小于0,给每个图层的变化添加不同的层级level,这样就实现了不同图层具有不同的速率
let level = (15 - parseInt(k)) * 0.5;
let new_translateX = element_data.translateX + x_offset / level
还有一些细节就是,B站的第六张图片向右移动透明度逐渐增加,向左保持透明也就是opacity为0,第九、十章图片效果相反,所以添加了一些判断代码,注意:k是索引,从0开始,所以第六张图片的k是5,还有一个问题我没有解决的就是,这三张特殊的图片的opacity是逐渐变化的,而我没有找到最合适的效果,所以给他们设定了一个固定值0.333和0.44,目的达到了反正
if (k == 5 && x_offset < 0) {
new_opacity = 0.333
} else if ((k == 8 || k == 9) && x_offset > 0) {
new_opacity = 0.44
} else {
new_opacity = element_data.opacity
}
然后就是修改img内联样式translateX的值
element.children[0].style = `transform: scale(1)
translate( ${new_translateX}px, ${element_data.translateY}px )
rotate(0deg);
filter: blur(${element_data.filter_blur}px);
opacity: ${new_opacity};
`
这里,element不是img标签,而是img标签外面的div,所以通过.children[0]获取到img元素,进而改变其样式值
到这里,操作就完成了,然后,有什么意见,欢迎大家指正小白,毕竟啊,我是要成为代码王的男人,会虚心接受大家的指导的