本案例构建一个多页面相册博客项目,包含首页(横滑相册)、分类页(按年 Tab 切换)、博客页(双栏布局)、联系页(带 Popover 提示的表单)。重点练习导航栏、Tab 切换、灯箱插件(Swipebox)、表单校验提示(Popover)等组件的组合使用。

一、案例概述

1.1 业务背景

相册博客是摄影爱好者展示作品的常见形态。本项目模拟一个”作品集 + 博客”混合站点:首页以横滑跑马灯展示精选作品,分类页用Tab 按年份切换,博客页采用左侧文章 + 右侧推荐的经典双栏,联系页通过 Popover 给表单加即时提示。

1.2 涉及知识点

知识点 在本案例中的作用
导航栏 .navbar 多页面顶部导航
选项卡 .nav-pills + .tab-content 分类页按年份切换
Swipebox 灯箱插件 相册图片点击放大
Popover 提示 联系页表单即时提示
列表组 .list-group 推荐区 / 联系方式
原生 JS 动画 首页跑马灯自动滚动
表单 .form-group 联系页输入校验

1.3 页面结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
相册博客
├── 首页 index.html
│ ├── 顶部导航栏
│ ├── 横滑跑马灯
│ └── 跑马灯左右控制按钮
├── 分类页 class.html
│ ├── 顶部导航栏
│ └── 年份 Tab(2020 / 2019 / 2018)
├── 博客页 blog.html
│ ├── 左侧:文章主体
│ └── 右侧:推荐旅游胜地
└── 联系页 contact.html
├── 左侧:留言表单
└── 右侧:联系方式

二、实现步骤拆解

2.1 顶部导航栏

四个页面共用同一段导航代码,包含品牌 logo、菜单、搜索框。折叠按钮用于移动端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a href="index.html" class="navbar-brand">
<img src="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4031816500,2465166800&fm=26&gp=0.jpg" width="45" alt="">
</a>
<button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbarContent">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active"><a href="index.html" class="nav-link">首页</a></li>
<li class="nav-item"><a href="class.html" class="nav-link">分类</a></li>
<li class="nav-item"><a href="blog.html" class="nav-link">博客</a></li>
<li class="nav-item"><a href="contact.html" class="nav-link">联系</a></li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input type="search" class="form-inline mr-sm-2" placeholder="搜索">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>
</form>
</div>
</nav>

2.2 首页跑马灯

通过 JS 克隆 <li> 实现无缝循环,再给 oUl 一个大宽度让它横向铺开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="container">
<div id="div1">
<ul id="ul1" class="py-3">
<li>
<a href="http://pic1.win4000.com/wallpaper/3/587084e3946ee.jpg" class="swipebox" title="2028年">
<img src="http://pic1.win4000.com/wallpaper/3/587084e3946ee.jpg" alt="image" class="img-fluid">
</a>
</li>
<li>
<a href="http://pic1.win4000.com/wallpaper/c/54783c8bb636f.jpg" class="swipebox" title="2028年">
<img src="http://pic1.win4000.com/wallpaper/c/54783c8bb636f.jpg" alt="image" class="img-fluid">
</a>
</li>
<!--更多 <li>...-->
</ul>
<div class="btn-box text-center mb-2">
<a href="javascript:void(0);" id="btn1" class="mr-5"><i class="fa fa-hand-o-left fa-2x"></i></a>
<a href="javascript:void(0);" id="btn2"><i class="fa fa-hand-o-right fa-2x"></i></a>
</div>
</div>
</div>

2.3 引入 Swipebox 灯箱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!--swipebox插件-->
<link rel="stylesheet" href="./swipebox-master/src/css/swipebox.css">
<script src="swipebox-master/lib/jquery-2.1.0.min.js"></script>
<script src="swipebox-master/src/js/jquery.swipebox.js"></script>
<script>
// 跑马灯自动滚动
window.onload = function () {
var oDiv = document.getElementById('div1');
var oUl = document.getElementById('ul1');
var speed = 2;
oUl.innerHTML += oUl.innerHTML; // 克隆一份实现无缝循环
var oLi = document.getElementsByTagName('li');
oUl.style.width = oLi.length * 160 + 'px';
var oBtn1 = document.getElementById('btn1');
var oBtn2 = document.getElementById('btn2');

function move() {
if (oUl.offsetLeft < -(oUl.offsetWidth / 2)) oUl.style.left = 0;
if (oUl.offsetLeft > 0) oUl.style.left = -(oUl.offsetWidth / 2) + 'px';
oUl.style.left = oUl.offsetLeft + speed + 'px';
}
oBtn1.addEventListener('click', function () { speed = -2; }, false);
oBtn2.addEventListener('click', function () { speed = 2; }, false);
setInterval(move, 30);
};
// 绑定了 .swipebox 类
jQuery(function ($) {
$('.swipebox').swipebox({
useCSS: true, useSVG: true, initialIndexOnArray: 0,
hideCloseButtonOnMobile: false, removeBarsOnMobile: true,
hideBarsDelay: 3000, loopAtEnd: false
});
});
</script>

2.4 分类页:年份 Tab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div class="container">
<div class="menu bg-dark">
<ul class="nav nav-pills my-4 p-2" id="myTab">
<li><a href="#pills-home" class="ab" data-toggle="pill">2020年</a></li>
<li><a href="#pills-profile" data-toggle="pill">2019年</a></li>
<li><a href="#pills-contact" data-toggle="pill">2018年</a></li>
<li><a href="javascript:void(0);">更多</a></li>
</ul>
</div>

<div class="tab-content">
<div class="tab-pane fade show active" id="pills-home">
<div class="row list">
<div class="col-4">
<a href="http://pic1.win4000.com/wallpaper/4/53a2af0995d64.jpg" class="swipebox" title="2020年">
<img src="http://pic1.win4000.com/wallpaper/4/53a2af0995d64.jpg" alt="image" class="img-fluid">
</a>
</div>
<!--更多 col-4...-->
</div>
</div>
<div class="tab-pane fade" id="pills-profile"><!-- 2019 年图片 --></div>
<div class="tab-pane fade" id="pills-contact"><!-- 2018 年图片 --></div>
</div>
</div>

2.5 博客页:双栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div class="border row bg-white m-0 px-3 pt-4 pb-5 blog-border">
<!--左侧文章展示部分-->
<div class="col-8">
<h4><i class="fa fa-smile-o mr-2"></i><span>我的足迹</span></h4>
<hr>
<div class="mb-3">
<i class="fa fa-user-o"></i><span class="ml-1 mr-2">欢欢</span>
<i class="fa fa-clock-o"></i><span class="ml-1 mr-2">15天前</span>
<a href="javascript:void(0);" class="ml-1 mr-2"><i class="fa fa-commenting-o">156条</i></a>
</div>
<img src="http://img0.imgtn.bdimg.com/it/u=1101084170,1274803972&fm=26&gp=0.jpg" alt="" class="img-fluid mb-3">
<p class="retract">
  一个人旅行,一台相机足以、不理会繁杂的琐事,自由自在地,去体验一个城市、一段故事、留下一片欢笑。
</p>
</div>
<!--右侧推荐区-->
<div class="col-4">
<h4 class="shadow mb-4"><span class="mx-2">推荐旅游胜地</span><i class="fa fa-bicycle"></i></h4>
<ul class="list-group list-group-flush">
<li class="list-group-item border-top-0"><i class="fa fa-hand-o-right mr-3"></i>神秘奇幻、佳景荟萃的九寨沟</li>
<li class="list-group-item"><i class="fa fa-hand-o-right mr-3"></i>奇伟俏丽、灵秀多姿的黄山</li>
<li class="list-group-item"><i class="fa fa-hand-o-right mr-3"></i>青山碧水、银滩巨浪的三亚</li>
<li class="list-group-item"><i class="fa fa-hand-o-right mr-3"></i>山青、水秀、洞奇、石美的桂林山水</li>
<li class="list-group-item"><i class="fa fa-hand-o-right mr-3"></i>山水秀丽、景色宜人的杭州西湖</li>
</ul>
</div>
</div>

2.6 联系页:Popover 表单

通过 data-toggle="popover" 给每个输入框附加即时提示,初始化时调用 popover() 激活:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div class="row border bg-white m-0 px-3 pt-4 pb-5 blog-border">
<div class="col-12 col-lg-8 pb-5">
<h4><i class="fa fa-volume-control-phone mr-2"></i><span>你的联系方式</span></h4>
<hr>
<form>
<div class="form-group">
<input type="text" class="form-control w-75" placeholder="姓名"
data-toggle="popover" data-placement="right" data-content="请输入你的姓名">
</div>
<div class="form-group">
<input type="email" class="form-control w-75" placeholder="邮箱"
data-toggle="popover" data-placement="right" data-content="请输入你的邮箱">
</div>
<div class="form-group">
<input type="tel" class="form-control w-75" placeholder="手机号"
data-toggle="popover" data-placement="right" data-content="请输入你的手机号">
</div>
<div class="form-group">
<textarea class="form-control w-75" rows="5" placeholder="留言板"
data-toggle="popover" data-placement="right" data-content="留言板"></textarea>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
<div class="col-12 col-lg-4">
<h4 class="shadow mb-4"><i class="fa fa-phone-square mx-2"></i><span>联系我们</span></h4>
<div class="alert alert-primary"><i class="fa fa-qq mr-3"></i><span>867940410</span></div>
<div class="alert alert-info"><i class="fa fa-weixin mr-3"></i><span>刘飞</span></div>
<div class="alert alert-success"><i class="fa fa-mobile fa-2x mr-3"></i><span>12345678912</span></div>
<div class="alert alert-danger"><i class="fa fa-map-maker fa-2x mr-3"></i><span>北京千古摄影工作室</span></div>
</div>
</div>

<script>
$(function () { $('[data-toggle="popover"]').popover(); });
</script>

三、关键 CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
html{ background-color: lavender; }
#div1{ width: 100%; height: 300px; margin: 150px auto; position: relative; overflow: hidden; border: 2px solid white; background-color: white; }
#div1 ul{ height: 240px; position: absolute; left: 0; top: 0; overflow: hidden; list-style: none; background-color: white; }
#div1 ul li{ float: left; width: 360px; list-style: none; margin-left: 1.1rem; }
#div1 ul li img{ width: 360px; }
.btn-box{ position: relative; left: 0; top: 255px; }
.menu{ width: 275px; border-radius: 10px; }
#myTab{ list-style: none; }
#myTab li{ float: left; margin-left: 15px; }
#myTab li a{ color: #919191; }
.ab{ color: #00A862 !important; }
.list{ min-width: 600px; }
.list div{ margin-bottom: 20px; }
.blog-border{ border-radius: 10px; }
.retract{ text-indent: 2rem; }
.shadow{ line-height: 48px; padding: 0 10px; margin-bottom: 20px; border-top: 2px solid #d7d7d7; border-bottom: 2px solid #ffffff; border-radius: 5px; }

四、关键技术解析

4.1 跑马灯无缝循环

核心三步:

  1. oUl.innerHTML += oUl.innerHTML 克隆一份图片。
  2. oUl.offsetLeft < -oUl.offsetWidth/2 时把 left 归零(视觉上无感知)。
  3. oUl.offsetLeft > 0(向右滑过头)时跳到负半幅起始位置。

4.2 Swipebox 灯箱

Swipebox 是一个轻量 jQuery 灯箱插件,绑定 .swipebox 类后点击图片即可弹出全屏大图,支持左右切换、关闭按钮、自动隐藏工具栏等。配置项在 swipebox({...}) 中传入。

4.3 Tab + data-toggle=”pill”

nav-pills + tab-content + tab-pane 三个组合是 Bootstrap 4 的胶囊型 Tab:

  • data-toggle="pill" 绑定点击行为。
  • .tab-panefade show active 切换淡入淡出与可见性。
  • 父容器 id 决定 href 跳转目标。

4.4 Popover 提示

Bootstrap 的 Popover 组件比 Tooltip 更强大,支持标题、内容、位置、触发方式。给输入框加 data-toggle="popover" 即可声明提示,初始化时调用 .popover() 激活。

五、小结

本案例通过四类典型页面演练了相册博客的核心交互:

  • 跑马灯 + 灯箱展示作品。
  • Tab 按年份组织相册。
  • 双栏博客 + 推荐区。
  • Popover 表单即时提示。

建议扩展练习:

  • 把跑马灯改为 CSS @keyframes 动画版本。
  • 给博客页加评论列表与分页。
  • 把联系页表单改用 Bootstrap 的 .was-validated + :invalid 实现原生校验。