HTML&CSS&JS 手把手教你实现网页底部养鱼动态效果

在网页开发中,静态页面早已无法满足用户对交互体验的高要求。而 HTML5 Canvas 技术的出现,为前端开发者打开了创意之门 —— 它不仅能绘制静态图形,更能实现丰富的动态交互效果。本文将以 “网页底部养鱼动画” 为案例,从效果拆解、技术原理到代码实现,带你一步步打造出包含水面波动、鱼群游动、鼠标交互的生动效果,让你的网页瞬间充满活力。
 

20250914094335251-image

 

一、效果预览与核心目标

在动手前,我们先明确最终要实现的效果,让开发更有方向:

 

  • 视觉效果:网页底部固定显示一个 “水池” 区域,水面有自然的波动效果;多条鱼在水中自主游动,姿态随游动方向自然变化。
  • 交互效果:鼠标在 “水池” 区域移动时,会触发水波涟漪;点击页面可翻转鱼的游动方向,增加互动趣味性。
  • 技术核心:基于 HTML5 Canvas 实现图形绘制与动画帧控制,通过 CSS 定位 “水池” 区域,用 JavaScript 模拟鱼类游动的物理行为与交互逻辑。

二、实现思路拆解

复杂的动态效果,拆解后会变得简单。这个养鱼动画可分为 4 个核心模块,我们逐一突破:

 

  1. 页面布局搭建:用 HTML 定义 “水池” 容器,通过 CSS 固定其在网页底部的位置,设置尺寸与背景样式。
  2. Canvas 初始化:在 “水池” 容器中创建 Canvas 元素,作为绘制水面、鱼类的 “画布”,并适配容器尺寸。
  3. 动态效果实现
    • 水面波动:通过 Canvas 绘制正弦曲线,循环更新曲线参数模拟波动。
    • 鱼类游动:定义鱼的类(包含位置、速度、姿态等属性),通过定时器更新鱼的位置,实现游动动画。
    • 交互逻辑:监听鼠标移动事件生成水波,监听点击事件翻转鱼的游动方向。
  4. 性能优化:使用 requestAnimationFrame 替代 setInterval 控制动画帧,避免页面卡顿,提升流畅度。

三、完整代码实现

接下来,我们从 HTML、CSS、JavaScript 三个维度,写出可直接运行的代码,并标注关键逻辑的解释。

1. HTML 布局:搭建基础结构

HTML 部分只需定义 “水池” 容器,并引入所需的外部资源(CSS 样式、jQuery 库、自定义 JS 脚本)。注意:jQuery 仅用于简化 DOM 操作,也可使用原生 JS 替代。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>网页底部养鱼效果</title>
    <!-- 引入CSS样式 -->
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <!-- 水池容器:所有动态效果将在这个容器内实现 -->
    <div id="fish-container" class="container"></div>

    <!-- 引入jQuery(简化DOM操作) -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <!-- 引入自定义JS逻辑 -->
    <script src="./script.js"></script>
</body>
</html>

2. CSS 样式:固定容器位置与外观

通过 CSS 将 “水池” 容器固定在网页底部,设置宽度为 100%、高度为屏幕的 50%,并隐藏页面滚动条(避免容器位置偏移)。
/* 重置页面默认样式:消除边距,避免滚动条干扰 */
html, body {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden; /* 隐藏页面滚动条 */
}

/* 水池容器样式:固定在底部,作为动画载体 */
.container {
    position: fixed; /* 固定定位,不随页面滚动 */
    bottom: 0; /* 距离页面底部0px */
    left: 0; /* 距离页面左侧0px */
    width: 100%;
    height: 50%; /* 容器高度为屏幕的50% */
    background-color: #f0f8ff; /* 浅蓝色背景,模拟水面底色 */
    z-index: 1; /* 确保容器在页面底层,不遮挡其他内容 */
}

/* Canvas 画布样式:铺满水池容器 */
#fish-canvas {
    width: 100%;
    height: 100%;
    display: block; /* 消除Canvas默认的inline-block空隙 */
}

3. JavaScript 逻辑:实现动态与交互

JS 是整个效果的核心,我们将分步骤实现 “Canvas 初始化”“鱼的类定义”“水面波动”“交互逻辑” 四个部分,代码中关键步骤已标注注释:

(1)Canvas 初始化与全局配置

首先创建 Canvas 元素并添加到水池容器中,定义全局参数(如水面波动幅度、鱼的数量等),为后续绘制做准备。
$(function() {
    // 1. 获取水池容器,创建Canvas元素
    const container = $('#fish-container')[0];
    const canvas = document.createElement('canvas');
    canvas.id = 'fish-canvas';
    container.appendChild(canvas);
    const ctx = canvas.getContext('2d'); // 获取Canvas绘图上下文

    // 2. 适配容器尺寸:确保Canvas铺满水池
    function resizeCanvas() {
        canvas.width = container.offsetWidth;
        canvas.height = container.offsetHeight;
    }
    resizeCanvas();
    window.addEventListener('resize', resizeCanvas); // 窗口 resize 时重新适配

    // 3. 全局配置参数:可根据需求调整
    const config = {
        waveAmplitude: 5, // 水面波动幅度(像素)
        waveFrequency: 0.02, // 水面波动频率
        waveSpeed: 0.1, // 水面波动速度
        fishCount: 8, // 鱼的数量
        fishSizeRange: [15, 30], // 鱼的大小范围(像素)
        threshold: 50 // 鱼超出屏幕的判定阈值(避免鱼突然消失)
    };

    // 4. 水面波动状态:记录波动的时间参数
    let waveTime = 0;

    // 后续代码将在这里继续...
});

(2)定义鱼的类:模拟物理行为

通过 FISH 类定义鱼的属性(位置、速度、方向、大小等)和方法(初始化、绘制、更新位置),让鱼具备 “自主游动” 的能力。
// 鱼的类:封装鱼的属性与行为
class Fish {
    constructor(renderer) {
        this.renderer = renderer; // 传入Canvas渲染上下文与尺寸信息
        this.init(); // 初始化鱼的状态
    }

    // 初始化:随机设置鱼的位置、速度、方向
    init() {
        const { width, height, reverse, threshold } = this.renderer;
        // 随机确定鱼的初始游动方向(true:从右向左,false:从左向右)
        this.direction = Math.random() < 0.5;
        // 初始X坐标:从屏幕外进入(避免鱼从屏幕中间突然出现)
        this.x = this.direction ? (width + threshold) : -threshold;
        // 初始Y坐标:根据reverse状态决定在水池的上半部分或下半部分
        this.y = reverse 
            ? this.getRandomValue(height * 1/10, height * 4/10) 
            : this.getRandomValue(height * 6/10, height * 9/10);
        // 速度:X轴速度(方向决定正负),Y轴速度(上下浮动)
        this.vx = this.getRandomValue(2, 5) * (this.direction ? -1 : 1);
        this.vy = reverse 
            ? this.getRandomValue(1, 3) 
            : this.getRandomValue(-3, -1);
        // 加速度:模拟鱼游动的“惯性”,让运动更自然
        this.ay = reverse 
            ? this.getRandomValue(0.02, 0.08) 
            : this.getRandomValue(-0.08, -0.02);
        // 鱼的大小:随机在配置范围内
        this.size = this.getRandomValue(config.fishSizeRange[0], config.fishSizeRange[1]);
        // 状态标记:是否超出屏幕(超出后重新初始化)
        this.isOut = false;
    }

    // 工具方法:获取指定范围内的随机数
    getRandomValue(min, max) {
        return Math.random() * (max - min) + min;
    }

    // 绘制鱼:用Canvas绘制简单的鱼形状(身体+尾巴+眼睛)
    draw(ctx) {
        ctx.save(); // 保存当前绘图状态
        ctx.translate(this.x, this.y); // 将原点移动到鱼的位置

        // 1. 绘制鱼身体(椭圆形)
        ctx.fillStyle = '#ff6347'; // 红色鱼身
        ctx.beginPath();
        ctx.ellipse(0, 0, this.size, this.size / 2, 0, 0, Math.PI * 2);
        ctx.fill();

        // 2. 绘制鱼尾(三角形)
        ctx.fillStyle = '#ff4500'; // 深红色鱼尾
        ctx.beginPath();
        const tailX = this.direction ? this.size : -this.size; // 鱼尾方向与游动方向一致
        ctx.moveTo(tailX, 0);
        ctx.lineTo(tailX + (this.direction ? 10 : -10), -this.size / 2);
        ctx.lineTo(tailX + (this.direction ? 10 : -10), this.size / 2);
        ctx.closePath();
        ctx.fill();

        // 3. 绘制鱼眼(圆形)
        ctx.fillStyle = '#000'; // 黑色眼睛
        ctx.beginPath();
        const eyeX = this.direction ? -this.size / 2 : this.size / 2; // 眼睛在头部
        ctx.arc(eyeX, -this.size / 4, this.size / 8, 0, Math.PI * 2);
        ctx.fill();

        ctx.restore(); // 恢复绘图状态
    }

    // 更新鱼的位置:每帧调用,实现游动动画
    update() {
        const { width, threshold } = this.renderer;
        // 1. 更新速度(加入加速度,让运动更自然)
        this.vy += this.ay;
        // 2. 更新位置
        this.x += this.vx;
        this.y += this.vy;
        // 3. 边界检测:鱼超出屏幕后,重新初始化(让鱼循环游动)
        if (this.direction && this.x < -threshold) {
            this.isOut = true;
            this.init();
        } else if (!this.direction && this.x > width + threshold) {
            this.isOut = true;
            this.init();
        }
    }
}

(3)水面波动与鱼群管理

实现水面波动的绘制逻辑,创建鱼群实例,并通过 requestAnimationFrame 循环更新动画帧,让效果 “动” 起来。
// 继续在 $(function() { ... }) 内部编写

// 5. 初始化渲染器信息:传递给鱼的类,包含Canvas尺寸与状态
const renderer = {
    ctx,
    width: canvas.width,
    height: canvas.height,
    reverse: false, // 是否翻转鱼的游动区域(点击时切换)
    threshold: config.threshold
};

// 6. 创建鱼群:根据配置的数量生成鱼实例
const fishGroup = [];
for (let i = 0; i < config.fishCount; i++) {
    fishGroup.push(new Fish(renderer));
}

// 7. 绘制水面波动:用正弦曲线模拟水面
function drawWave() {
    const { width, height } = renderer;
    ctx.save();
    ctx.fillStyle = 'rgba(135, 206, 235, 0.5)'; // 浅蓝色半透明,模拟水面
    ctx.beginPath();
    // 从左下角开始绘制
    ctx.moveTo(0, height);
    // 绘制正弦曲线:x从0到width,y随waveTime变化
    for (let x = 0; x < width; x++) {
        const y = height - config.waveAmplitude * Math.sin(x * config.waveFrequency + waveTime);
        ctx.lineTo(x, y);
    }
    // 闭合路径,填充水面区域
    ctx.lineTo(width, height);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
    // 更新波动时间参数,让水面持续动起来
    waveTime += config.waveSpeed;
}

// 8. 动画主循环:每帧更新并绘制所有元素
function animate() {
    // 清空Canvas:避免上一帧的图像残留
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // 绘制水面波动
    drawWave();
    // 更新并绘制鱼群
    fishGroup.forEach(fish => {
        fish.update();
        fish.draw(ctx);
    });
    // 循环调用动画帧:浏览器会自动优化帧率
    requestAnimationFrame(animate);
}

// 启动动画
animate();

(4)添加交互逻辑:鼠标与点击事件

监听鼠标移动事件生成水波,监听点击事件翻转鱼的游动方向,让效果更具交互性。
// 继续在 $(function() { ... }) 内部编写

// 9. 鼠标交互:移动鼠标时生成水波涟漪
let mouseX = 0;
let mouseY = 0;
let isMouseActive = false; // 标记鼠标是否在容器内

// 监听鼠标进入容器事件
container.addEventListener('mouseenter', (e) => {
    isMouseActive = true;
    const rect = container.getBoundingClientRect();
    mouseX = e.clientX - rect.left;
    mouseY = e.clientY - rect.top;
});

// 监听鼠标离开容器事件
container.addEventListener('mouseleave', () => {
    isMouseActive = false;
});

// 监听鼠标移动事件:更新鼠标位置
container.addEventListener('mousemove', (e) => {
    const rect = container.getBoundingClientRect();
    mouseX = e.clientX - rect.left;
    mouseY = e.clientY - rect.top;
});

// 修改 drawWave 函数,添加水波涟漪效果
function drawWave() {
    const { width, height } = renderer;
    ctx.save();
    ctx.fillStyle = 'rgba(135, 206, 235, 0.5)';
    ctx.beginPath();
    ctx.moveTo(0, height);
    for (let x = 0; x < width; x++) {
        let y = height - config.waveAmplitude * Math.sin(x * config.waveFrequency + waveTime);
        // 如果鼠标在容器内,在鼠标位置添加额外波动(涟漪效果)
        if (isMouseActive) {
            const distance = Math.abs(x - mouseX);
            if (distance < 100) { // 只在鼠标周围100px范围内产生涟漪
                y -= config.waveAmplitude * 2 * Math.cos(distance * 0.05);
            }
        }
        ctx.lineTo(x, y);
    }
    ctx.lineTo(width, height);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
    waveTime += config.waveSpeed;
}

// 10. 点击交互:点击页面翻转鱼的游动区域
$(document).on('click', () => {
    renderer.reverse = !renderer.reverse;
    // 重新初始化所有鱼的位置,让翻转效果立即生效
    fishGroup.forEach(fish => {
        fish.init();
    });
});

四、效果优化与扩展建议

当前效果已能满足基础需求,你还可以从以下方向优化或扩展,让效果更惊艳:

 

  1. 视觉优化
    • 给鱼添加渐变颜色,让鱼身更有立体感;
    • 水面添加光影效果(如阳光反射),用 ctx.shadowBlur 实现;
    • 增加鱼的游动姿态变化(如转弯时的角度旋转)。
  2. 交互扩展
    • 点击水面时生成一条新鱼;
    • 鼠标追逐鱼群(鱼会躲避鼠标);
    • 加入触摸事件,适配移动端。
  3. 性能优化
    • 当鱼超出屏幕时,暂停绘制(而非重新初始化);
    • 使用 canvas.toDataURL() 缓存静态元素(如鱼的形状),减少重复绘制计算。

五、技术总结

通过这个案例,我们不仅实现了一个生动的养鱼动画,更深入理解了 HTML5 Canvas 的核心能力:

 

  • Canvas 是 “像素级” 的绘图工具,通过 getContext('2d') 可实现复杂的图形与动画;
  • 动态效果的本质是 “逐帧更新”,requestAnimationFrame 是优化动画流畅度的关键;
  • 物理模拟(如鱼的速度、加速度)能让动画更自然,贴近现实世界的运动规律。

 

前端开发的魅力在于 “将创意转化为可见的体验”,而 Canvas 正是实现这一目标的强大工具。无论是游戏、数据可视化还是交互动画,Canvas 都能为你的网页注入新的活力。期待你基于本文的思路,创造出更有创意的作品!
🎀 🌸

📜 重要提示:
如有解压密码:看下载页、看下载页、看下载页。
源码工具资源类具有可复制性: 建议具有一定思考和动手能力的用户购买。
请谨慎考虑: 小白用户和缺乏思考动手能力者不建议赞助。
虚拟商品购买须知: 虚拟类商品,一经打赏赞助,不支持退款。请谅解,谢谢合作!
邻兔跃官网:lt.lintuyue.com(如有解压密码看下载页说明)。

文章版权声明 1、本网站名称:邻兔跃lT
2、本站永久网址:https://lt.lintuyue.com/
3、本站内容主要来源于互联网优质资源整合、网友积极投稿以及部分原创内容,仅供内部学习研究软件设计思想和原理使用,学习研究后请自觉删除,请勿传播,因未及时删除所造成的任何后果责任自负,如有侵权,请联系站长进行删除处理。
4、本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
5、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
6、本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容