仿B站首页头部动画的实现

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>

我就在这里讲讲我的思路吧,因为效果也不是完全一致的,就说一下在写代码过程中遇到的一些问题

  1. 首先,我先看的B站源代码,没有找到控制该效果的js文件,所以就先将其html框架copy下来了,当然,它的样式是这样的
    在这里插入图片描述我的代码在它的基础上将img标签中的内联样式进行了提取,至于为什么要这样做,一会会说明,css样式是一样的,就直接copy引入即可
  2. 去B站首页观察这个效果和它的源代码的时候,我发现了以下几点:首先,这一张一张的图片的移动速度肯定是不相同的,第二,在我鼠标移动进去这个区域的时候,图片的translate的值是不变的,也就是说,鼠标第一次进入这个区域的时候,这个位置就是移动的初始位置,移动过程中,改变相应速率的translate的值,第三,鼠标移出以后,恢复到原来的状态
  3. 有了以上信息,就开始着手做了,首先,为了将所有的图片组合成一个静态页面,就免不了对图片的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元素,进而改变其样式值

到这里,操作就完成了,然后,有什么意见,欢迎大家指正小白,毕竟啊,我是要成为代码王的男人,会虚心接受大家的指导的

  • 10
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
拥塞控制算法涉及到网络传输方面的问题,与动画没有直接关系。但是,可以通过编写程序模拟网络传输的过程,从而实现拥塞控制算法的仿动画效果。 下面是一个简单的示例,使用C语言实现了TCP Tahoe拥塞控制算法的仿动画。 ```c #include <stdio.h> #include <stdlib.h> #include <time.h> #define MAX_PACKET_SIZE 1024 #define MAX_WINDOW_SIZE 10240 #define MAX_SEQ_NUM 32768 typedef struct { int seq_num; // 数据包序号 int size; // 数据包大小 int ack; // 确认序号 } packet; int main() { int cwnd = 1; // 拥塞窗口大小 int ssthresh = MAX_WINDOW_SIZE; // 慢启动门限 int unacked = 0; // 未确认数据包数 int next_seq_num = 0; // 下一个数据包序号 int last_ack_num = -1; // 上一个确认序号 int total_sent = 0; // 总发送数据量 int total_recv = 0; // 总接收数据量 int total_lost = 0; // 总丢失数据量 packet send_buffer[MAX_SEQ_NUM]; // 发送缓冲区 int send_head = 0; // 发送缓冲区头部位置 int send_tail = 0; // 发送缓冲区尾部位置 packet recv_buffer[MAX_SEQ_NUM]; // 接收缓冲区 int recv_head = 0; // 接收缓冲区头部位置 int recv_tail = 0; // 接收缓冲区尾部位置 srand(time(NULL)); // 初始化随机数生成器 while (1) { // 发送数据包 while (next_seq_num < send_tail + cwnd && next_seq_num < MAX_SEQ_NUM) { packet p; p.seq_num = next_seq_num; p.size = rand() % MAX_PACKET_SIZE + 1; p.ack = -1; send_buffer[next_seq_num] = p; next_seq_num++; total_sent += p.size; unacked++; } // 模拟数据包丢失 if (rand() % 100 < 10 && unacked > 0) { int lost_seq_num = rand() % unacked + send_head; packet lost_packet = send_buffer[lost_seq_num]; total_lost += lost_packet.size; unacked -= lost_seq_num - send_head + 1; send_head = lost_seq_num + 1; ssthresh = cwnd / 2; cwnd = 1; } // 接收确认 while (recv_head < recv_tail) { packet p = recv_buffer[recv_head]; if (p.ack > last_ack_num) { unacked -= p.ack - last_ack_num; last_ack_num = p.ack; if (cwnd < ssthresh) { cwnd *= 2; } else { cwnd++; } } recv_head++; total_recv += p.size; } // 发送确认 int ack_num = last_ack_num; while (ack_num < next_seq_num && ack_num < last_ack_num + cwnd) { packet p = send_buffer[ack_num]; p.ack = ack_num + 1; recv_buffer[recv_tail] = p; recv_tail++; ack_num++; } // 判断结束条件 if (last_ack_num == MAX_SEQ_NUM - 1) { break; } } printf("Total sent: %d\n", total_sent); printf("Total received: %d\n", total_recv); printf("Total lost: %d\n", total_lost); return 0; } ``` 这段代码模拟了TCP Tahoe拥塞控制算法的过程,通过随机生成数据包大小和丢包率,以及模拟确认过程,实现了简单的仿动画效果。你可以根据自己的需要进行改进和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值