英雄联盟官网案例
[TOC]
视频链接:https://www.bilibili.com/video/BV1qS4y1z7iu
资源链接:https://pan.baidu.com/s/1H6NYwlmKiZSf4dN4ZqXhqg?pwd=ryuk 提取码:ryuk
掌握
HTML5/CSS3
核心知识及实战技巧掌握
CSS3
中浮动/定位等布局方案掌握
CSS3
中的transition
和animation
掌握基于
flex
弹性盒模型的布局方案同行排列的
N
种实现方案及优缺点对比
CSS3
样式编写中的模块思想及封装处理掌握
JS
(包含ES6+
)、DOM
操作等核心知识掌握
DOM
事件的核心知识及事件委托掌握基于
JS+CSS3
动画的实战处理方案掌握
Promise/async/await
异步管控方案掌握
Ajax/Axios
的核心应用及数据绑定基于
JS
管控video
视频播放的解决方案基于单例设计模式/
ESModule
实现模块化编程基于
JS
实现页卡切换:两种不同处理思想基于
JS
实现多种形态轮播图案例开发基于
iscroll
插件实现局部滚动基于
JS
实现“鼠标滑动的图片幻灯片”特效基于
JS
打造炫酷的侧边栏楼层导航人性化交互设计方案:
Loading&
防抖&
节流前端性能优化方案:图片懒加载
&
骨架屏&Skeleton
英雄联盟官网案例
模块化思想
基于
vue/react/webpack
工程化开发(项目中主流思想)不想用工程化开发的方式,只想自己简单的写法页面,如何实现模块化呢?
HTML
- 分版块设置结构(就写在一起了)
CSS
基于前缀区分模块
每个模块单独写在
CSS
文件中,最后不同模块统一导入到入口文件里,并利用less/sass
的嵌套语法简化前缀书写首页
index.html
中导入index.less
入口文件,同时通入编译less
的js
文件index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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>英雄联盟官网</title>
<!-- 导入less -->
<link res="stylesheet/less" href="./css/index.less"></link>
<script src="css/less.min.js"></script>
</head>
<body>
</body>
</html>less
浏览器端编译文件下载:https://github.com/less/less.js/releases,下载后在`dist`文件夹index.less
1
2
3
4
5// 公用样式
@import './reset.min.css'; //格式化浏览器样式
@import './common.less'; // 公用样式
@import './head_box.less'; // 头部模块样式
JS
:不能直接写在不同的js
文件中然后导入,最后合并的时候仍然会有变量冲突,这里并不存在模块的概念基于闭包的方式,实现模块和模块之间变量隔离,防止全局变量污染,也可导出某个模块的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 头部
let headModule = (function () {
let head_box = null,
n = 0
const query = function query () {
}
return {
query
}
})()
// 轮播图
(function() {
let swiper_box = null,
n = 1
headModule.query()
})()这种模式就叫做:单例设计模式
使用
<script type="module" src="./js/index.js"></script>
,直接让js
支持ES6Module
的模块化规范,这个声明只会在新版本浏览器中生效。此时在index.js
就可以直接使用import
语法了。拓展:https://www.freesion.com/article/15861435156/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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>英雄联盟官网</title>
<!-- 导入less -->
<link res="stylesheet/less" href="./css/index.less"></link>
<script src="css/less.min.js"></script>
</head>
<body>
<!-- 导入js入口文件 -->
<script type="module" src="./js/index.js"></script>
</body>
</html>index.js
1
2import './head_box.js' // 不是webpack环境,后缀要加上
import './swiper_container.js'
其他准备:
axios、isScroll、qs
在webpack
中挂载到window
上并打包成main.min.js
文件,方便使用1
2
3
4
5
6
7import Iscroll from 'iscroll'
import axios from 'axios'
import qs from 'qs'
window.Iscroll = Iscroll
window.qs = qs
window.axios = axios封装
axios
成http.js
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62const http = axios.create({
// 统一请求前缀
baseURL: 'http://127.0.0.1:8888',
// 是否允许跨域时候携带资源凭证
withCredentials: false,
// 请求成功的状态码校验规则
validateStatus: status => {
return status >= 200 && status < 400;
},
// 请求头统一处理「设置请求主体专递给服务器的数据格式」
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
// POST系列请求,请求主体传递信息的格式化
transformRequest: (data, headers) => {
if (data !== null && typeof data === "object") {
const ct = headers['Content-Type'];
if (ct === "application/x-www-form-urlencoded") {
return qs.stringify(data);
}
}
return data;
}
});
// 请求拦截器:向服务器发送请求之前
http.interceptors.request.use(config => {
// ...
return config;
});
// 响应拦截器:获取到响应信息 ~ 自己处理业务逻辑之间
http.interceptors.response.use(response => {
// 获取响应主体信息
return response.data;
}, reason => {
// 失败的统一处理「一般就是做相关的提示或者其它操作」
let response = reason.response;
if (response) {
// 服务器有返回值,但是状态码不符合validateStatus校验规则
switch (response.status) {
case 400:
// ...
break;
case 401:
// ...
break;
}
} else {
// 服务器没有返回任何的信息
if (reason && reason.code === "ECONNABORTED") {
// 超时或者请求中断
}
if (!navigator.onLine) {
// 网络出现故障
}
}
return Promise.reject(reason);
});
// ES6Module中模块的导出:我想把当前模块中的某个方法,某些东西暴露出去供其他模块调用 export & export default
export default http;如果当前某个模块在另外一个模块导入过了,以后在其他模块再次导入的话,最后浏览器渲染的时候,也只按导入一次处理
拓展:
html
也可以进行模块化,但要基于第三方模板引擎,要学一些语法,做模板渲染,相对而言自己在js
中动态拼接会简单点。直接将html
写在一起也是没问题的数据不直接写死,后台数据在
admin
文件夹(为了练习到axios
)Node
版本>12.0.0
- 启动:
node server.js
,注意不能关窗口,如果想要关闭窗口服务还在,安装npm i pm2
,启动pm2 start server.js --name lol
,结束np2 stop lol
- 可以学习一下,
mock
数据怎么简单搭建成api
服务
备注:实际真正开发的时候,还是会使用webpack
静态资源打包器来进行开发的
头部导航模块
搭结构
尽可能减少层级嵌套,嵌套越深,性能消耗越大(涉及到浏览器底层渲染机制,
V8
引擎是怎么渲染的,从DOM
树到CSSOM
树,再到RenderTree
)尽可能标签语义化,让合理的标签做合适的事情,利于
seo
优化H5
中都有哪些标签(要背,烂熟于心):画结构
我们用
vscode
的liveServer
启动页面根据结构写代码
index.html
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<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>英雄联盟官网</title>
<!-- 导入less -->
<link rel="stylesheet/less" href="./css/index.less"></link>
<script src="css/less.min.js"></script>
</head>
<body>
<script src="./js/main.min.js"></script>
<!-- 导入js入口文件 -->
<script type="module" src="./js/index.js"></script>
<!-- 头部区域开始 -->
<div class="head-box">
<!-- 顶部 -->
<div class="common-head">
<!-- LOGO -->
<a href="" class="head-logo">
<img src="images/logo-public.png" alt="">
</a>
<!-- NAV -->
<nav class="head-nav">
<a href=""><span></span><span></span></a>
<a href=""><span></span><span></span></a>
<a href=""><span></span><span></span></a>
<a href=""><span></span><span></span></a>
<a href=""><span></span><span></span></a>
</nav>
<!-- 按钮组 -->
<a href="" class="head-search"></a>
<a href="" class="head-weixin">
<div class="wx-detail">
<img src="" alt="">
<p></p>
<img src="" alt="">
</div>
</a>
<!-- 个人中心 -->
<div class="head-person">
<div class="person-base">
<a href="" class="base-logo">
<img src="" >
</a>
<p></p>
</div>
<div class="person-detail">
<div class="detail-text">
<p></p>
<p></p>
</div>
<a class="detail-more" href="">
<i class="common-more"></i>
</a>
</div>
</div>
</div>
<!-- 顶部遮罩层 -->
<div class="common-head-mask">
</div>
<!-- 作者区域 -->
<div class="author-box">
<span></span>
<span></span>
<a href=""></a>
</div>
<!-- 背景按钮 -->
<a href="" class="background-btn">
</a>
<!-- 导航详情 -->
<div class="nav-detail">
<ul class="detail-con">
<li>
<a href=""></a>
<a href="">
<i class="icon-new"></i>
</a>
</li>
<li>
<a href=""></a>
<a href="">
<i class="icon-new"></i>
</a>
</li>
<li>
<a href=""></a>
<a href="">
<i class="icon-new"></i>
</a>
</li>
</ul>
</div>
</div>
<!-- 头部区域结束 -->
</body>
</html>index.less
1
2
3
4
5// 公用样式
@import './reset.min.css'; //格式化浏览器样式
@import './common.less'; // 公用样式
@import './head_box.less'; // 头部模块样式在结构上丰富内容:粘贴英雄联盟官网的文字、引入图片资源
导航栏一般都是写死的,不涉及数据绑定
index.html
完善数据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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
<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>英雄联盟官网</title>
<!-- 导入less -->
<link rel="stylesheet/less" href="css/index.less"></link>
<script src="css/less.min.js"></script>
</head>
<body>
<!-- 头部区域开始 -->
<header class="head-box">
<!-- 顶部 -->
<div class="common-head">
<!-- LOGO -->
<a href="https://lol.qq.com" class="head-logo">
<img src="images/logo-public.png" alt="">
</a>
<!-- NAV -->
<nav class="head-nav">
<a href="https://101.qq.com/#/hero" target="_blank">
<span>游戏资料</span>
<span>GAME INFO</span>
</a>
<a>
<span>商城/合作</span>
<span>STORE</span>
</a>
<a>
<span>社区互动</span>
<span>COMMUNITY</span>
</a>
<a href="https://lpl.qq.com/" target="_blank">
<span>赛事官网</span>
<span>ESPORTS</span>
</a>
<a>
<span>自助系统</span>
<span>SYSTEM</span>
</a>
</nav>
<!-- 按钮组 -->
<div class="head-search" title="搜索">
<span class="search-cancel"></span>
<input type="text" value="搜索lol.qq.com">
</div>
<a href="https://lol.qq.com/app/index.html" class="head-weixin" target="_blank">
<div class="wx-detail">
<img src="images/zm-qrcode.jpg">
<p>扫码下载掌上英雄联盟</p>
<img src="images/polo.gif">
</div>
</a>
<!-- 个人中心 -->
<div class="head-person">
<div class="person-base">
<a href="https://lol.qq.com/space/index.shtml" class="base-logo" target="_blank">
<img src="images/default.png" >
</a>
<span>
亲爱的召唤师,欢迎<a href="#">登录</a>
</span>
</div>
<div class="person-detail">
<div class="detail-text">
<p>登录并且绑定大区后才可以查看您的具体信息哦<br/>
(包括战绩、资产、声望)
</p>
<p>官网个人信息显示略有延迟,请以游戏数据为准</p>
</div>
<a class="detail-more" href="https://lol.qq.com/space/index.shtml" target="_blank">
进入个人中心
<i class="common-more"></i>
</a>
</div>
</div>
</div>
<!-- 顶部遮罩层 -->
<div class="common-head-mask">
</div>
<!-- 作者区域 -->
<div class="author-box">
<span>当前游戏版本</span>
<span>Ver 12.15</span>
<a href="https://lol.qq.com/gicp/news/410/37008396.html" target="_blank">版本详情</a>
</div>
<!-- 背景按钮 -->
<a href="https://lol.qq.com/act/a20220715starguardianpass/index.html?_code=507041" target="_blank" class="background-btn">
查看详情
</a>
<!-- 导航详情 -->
<div class="nav-detail">
<ul class="detail-con">
<li>
<a href="https://lol.qq.com/download.shtml?ADTAG=innercop.lol.web.top" target="_blank">游戏下载</a>
<a href="https://lol.qq.com/data/newbie-what.shtml" target="_blank">新手指引</a>
<a href="https://101.qq.com/#/page-index/tab-index" target="_blank">资料库</a>
<a href="https://lol.qq.com/tft/" target="_blank">
<i class="icon-new"></i>
云顶之弈
</a>
<a href="https://101.qq.com/" target="_blank">
<i class="icon-new"></i>
攻略中心
</a>
<a href="https://lol.qq.com/nexus/pc/" target="_blank">开发者基地</a>
<a href="https://lol.qq.com/act/a20171205hextech/" target="_blank">海克斯战利品库</a>
<a href="https://yz.lol.qq.com/" target="_blank">英雄联盟宇宙</a>
</li>
<li>
<a href="https://lol.qq.com/comm-htdocs/pay/new_index.htm?t=lol" target="_blank">点券充值</a>
<a href="https://daoju.qq.com/lol/" target="_blank">
<i class="icon-hot"></i>
道聚城
</a>
<a href="https://lolriotmall.qq.com/index.shtml?ADTAG=lol.rk.gw.top&adunionsid=gwtop" target="_blank">周边商城</a>
<a href="https://lol.qq.com/act/a20161011mvm/index.htm" target="_blank">LOL桌游</a>
<a href="https://cafe.qq.com/tpl/lol/index.html" target="_blank">网吧特权</a>
<a href="https://lol.qq.com/act/a20181124novel/?ADTAG=innercop.lol.web.top" target="_blank">电竞小说</a>
</li>
<li>
<a href="https://lol.qq.com/v/v2/index.shtml" target="_blank">视频中心</a>
<a href="https://lol.qq.com/act/a20160425wxlol/" target="_blank">官方微信</a>
<a href="https://weibo.com/lol" target="_blank">官方微博</a>
<a href="https://tr.lol.qq.com/" target="_blank">玩家创作馆</a>
<a href="https://lol.qq.com/act/a20180418yxny/" target="_blank">玩家服务</a>
<a href="https://lol.qq.com/act/a20200319clash/index.html?e_code=458230" target="_blank">
<i class="icon-new"></i>
LOL组队专区
</a>
<a href="https://lol.qq.com/author/#/homepage" target="_blank">
<i class="icon-new"></i>
作者入驻计划
</a>
<a href="https://lol.icreate.qq.com/v3" target="_blank">
<i class="icon-new"></i>
英雄联盟素材库
</a>
</li>
<li>
<a href="https://lpl.qq.com/es/msi/2022/" target="_blank">
<i class="icon-hot"></i>
季中冠军赛
</a>
<a href="https://lpl.qq.com/es/lpl/2022/" target="_blank">
<i class="icon-hot"></i>
LPL职业联赛
</a>
<a href="https://lpl.qq.com/es/ldl/2022/" target="_blank">LDL发展联赛</a>
<a href="https://lpl.qq.com/es/worlds/2021/" target="_blank">全球总决赛</a>
<a href="https://lpl.qq.com/act/a20220322city/" target="_blank">城市英雄争霸赛</a>
<a href="https://lpl.qq.com/act/a20211206demacia/index.html" target="_blank">德玛西亚杯</a>
<a href="https://lpl.qq.com/act/a20210928lcl/index.html" target="_blank">全国高校联赛</a>
<a href="https://lpl.qq.com/es/toc3" target="_blank">
<i class="icon-new"></i>
云顶之弈公开赛
</a>
</li>
<li>
<a href="https://lol.qq.com/act/a20210625icon/index.html" target="_blank">
<i class="icon-hot"></i>
图标自助领取
</a>
<a href="https://lol.qq.com/act/a20200710transferzone/index.html" target="_blank">
<i class="icon-new"></i>
转区系统
</a>
<a href="javascript:void(0)">
<i class="icon-hot"></i>
封号查询
<span style="display:none;">
<img src="./images/qr-fhcx.png" alt="封号查询">
<span>请前往“腾讯游戏安全中心”公众号-自助服务进行处罚查询</span>
</span>
</a>
<a href="https://lol.qq.com/act/a20200227logout/" target="_blank">账号注销</a>
<a href="https://lol.qq.com/act/a20190528lolscore/" target="_blank">
<i class="icon-hot"></i>
信誉分系统
</a>
<a href="https://lol.qq.com/act/a20150326dqpd/" target="_blank">服务器状态查询</a>
<a href="https://lol.qq.com/act/a20171108ambient/index.html" target="_blank">秩序殿堂</a>
<a href="https://lol.qq.com/act/a20191210super/index.shtml" target="_blank">峡谷之巅</a>
</li>
</ul>
</div>
</header>
<!-- 头部区域结束 -->
<script src="js/main.min.js"></script>
<!-- 导入js入口文件 -->
<script type="module" src="js/index.js"></script>
</body>
</html>效果:
写结构心得:
- 先写大结构,再写小结构,从外到内,将看到的页面内容,用合适的标签元素表达出来
- 鼠标悬停出来的结构,和悬停对象作为整体,写在一个结构中
- 可以同时定义类名,尽量语义化(熟练了的话,这一步直接就可以用预先定义好的类名,同时写样式和布局,如:
<div class="bg-red font-16 col-8"
)
从外到内,先写大结构
元素自身样式,可按以下顺序一一书写
- 先固定好元素整体大框架:元素的位置以及布局
- 控制显示与隐藏
- 控制位置
display
的类型- 考虑子元素的布局,要不要用
flex
- 考虑子元素的布局,要不要用
- 考虑要不要用到
position
属性
- 再控制大小
- 基于盒子模型顺序书写:
- 宽高、内边距、边框、外边距
- 背景图
- 基于盒子模型顺序书写:
- 最后完善细节:
- 字体相关:字体大小、颜色等
- 先固定好元素整体大框架:元素的位置以及布局
头部整体
.head-box
1 | // 头部样式 |
然后接着head-box
后面,加上前缀继续写样式(css
写法),从性能角度来讲,前缀越短性能越好,因为浏览器渲染css
时,选择器渲染顺序是从右到左,前缀越长,浏览器识别解析的东西就越多
但是呢,实际我们还不能忽略前缀问题,否则很容易出现样式冲突的问题,只能是尽可能减少
.head-box .comm-head .head-logo {}
这种方式写法上有点麻烦,我们可以用less、saas
等预处理器的嵌套语法
1 | // 头部样式 |
编译后的结果,是带有前缀的
新手写样式,通常是看到哪儿写到哪,该方式有助于学习
高手写样式,一开始是不会直接去写具体模块的样式,会先把整个页面的所有情况看完了,之后脑子里形成了雏形:这一块样式是公共的,这一块样式应该怎么写等等。然后先把公共的样式写了,写完后再写具体模块的样式,遇到已经写过的公共样式时,直接写个类名就好
如:
common.less
1 | .w1300 { |
给head-box
指定类名
1 | <div class="common-head w1300"> |
目前我们可能达不到这种效果,介于两者之前,先一步一步写,然后遇到可以抽离成公共写样式的就抽离
工程化开发时,一般会有基础样式库,自建库或第三方库,如
Tailwind.css
,演示网址:https://www.tailwindcss.cn/
父子元素嵌套的场景下
- 父元素不指定高度,可以子元素指定高度撑开父元素
- 子元素不指定高度,父元素指定高度,而子元素高度设置
100%
这里我们再给common-head
设置固定的高度撑开父元素
1 | .common-head { |
同时给遮罩层也设置高度,并设置渐变背景图片,同时抽离出公共样式
注意遮罩的层级要低一点
- 如果不给元素设置定位,设置层级是没有用的
- 设是
common-head
为relative
定位
1 | // 头部样式 |
现在要水平排列head-box
里的元素,这里用float
实现,在此之前给head-box
添加clearfix
样式类(在reset.min.css
中)清除子元素浮动给父元素带来的影响(子元素浮动脱离文档流,如果父元素没有设置高度,则父元素无法被子元素撑开)
1 | <header class="head-box clearfix"> |
设置浮动
1 | .common-head { |
调整.head-search, .head-weixin, .head-person
的结构顺序,这三个设置右浮动后,head-search
跑到了最右边,html
结构中和head-person
换一下位置
头部图标
head-logo
样式
1 | // LOGO |
将所有的img
元素设置为块级元素,写到common.less
中
1 | img { |
给img
元素添加alt="英雄联盟"
,利于seo
头部导航
head-nav
样式
在全局将所有盒子的盒子模型属性设置成border-box
,这样设置宽高时,包含了padding
和border
common.less
1 | * { |
1 | // head-nav |
head-nav
里面元素的大结构,也是同一行排列,这里我们用flex
布局
给head-nav
的每个子元素a
添加item
类名,添加nav-title
和nav-subtitle
类名
1 | <nav class="head-nav"> |
1 | // head-nav |
再设置每个item
里的大标题和小标题
1 | .item { |
效果:
导航详情
页面样式
给head-nav
添加悬浮显示效果
编写nav-detail
样式
- 通栏、半透明背景
- 每一栏的样式
ul.deail-con
也添加w1300
类,保证和head-box
垂直对齐- 距离左侧和顶部一定的空间,保证对齐
- 每个
li
水平排列,宽度和head-box
的每个nav
保持一致 - 图标用雪碧图,封装成公共样式,在所有用到图标的地方,添加公共样式类,然后利用
background-position
定位到指定的图标上,可以先用截图工具量个大概,再在调试工具中精确调整 - 利用
vertical-align
设置图标和文字基于中线对齐(看情况调整) - 最开始整个区域是不显示的
index.html
1 | <ul class="detail-con w1300"> |
common.less
1 | .sp-top { |
给所有要加图标的地方,加上样式类
1 | <i class="icon-new sp-top"></i> |
head_box.less
1 | // 导航详情 |
js
部分
需求:
鼠标滑到
nav
时显示详情,同时附带动画效果鼠标放在详情上时,也不消失
除此之外,详情消失
css
想要实现这样的效果,必须保证显示的内容是hover
元素的子元素,在结构上有限制head-nav
和head-detail
不是嵌套关系,这种需求要实现,只有一种方案:事件委托- 浏览器事件冒泡传播机制:子元素的点击事件被触发了,其父元素、祖先元素、
document
元素(一层层往上)的点击事件也都会被触发 - 事件对象:表示当前事件操作的信息,其中
target
值是事件源,表示真正触发事件的那个元素 - 事件委托:给上层元素绑定事件,通过事件对象的
target
不同,来进行不同的处理,不用每个元素都处理
- 浏览器事件冒泡传播机制:子元素的点击事件被触发了,其父元素、祖先元素、
index.js
1 | import './head_box.js'; |
head_box.js
1 | // 基于事件委托实现导航详情的控制 |
动画处理
transition
:修改样式就按动画轨迹处理head_box.less
1
2
3
4.nav-detail {
// 新增
transform: translateY(-10px);
transition: transform 0.2s;head_box.js
1
2
3
4
5if(ancestors.indexOf(headNav) > -1) {
navDetail.style.display = 'block'
navDetail.style.transform = 'translateY(0px)'; //这两个样式是会一起渲染,从none变成block的时候,动画已经结束了
return
}但并没有生效
当代浏览器有个渲染队列机制,会先把所有和修改样式的地方,收集到队列中统一渲染,减少
dom
的回流和重绘;可以在是收集过程中,通过获取元素的某个属性,来刷新渲染机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22document.addEventListener('mouseover', function (ev) {
let target = ev.target, // 获取到事件源
ancestors = getAncestors(target)
// 对事件源判断,进行逻辑处理
// console.log(target) // 拿到的事件源,可能是head-nav里的任意子元素,所以要获取当前事件源所有的祖先元素
// 两次通过js获取到的dom元素对象,虽然也是堆内存,但两次获取的结果是相等的
// 事件源是头部导航
if(ancestors.indexOf(headNav) > -1) {
navDetail.style.display = 'block'
navDetail.offsetWidth // 刷新浏览器的渲染队列机制,不用定时器
navDetail.style.transform = 'translateY(0px)';
return
}
// 事件源是详情
if(ancestors.indexOf(navDetail) > -1) return
// 其他事件源
navDetail.style.display = 'none'
navDetail.offsetWidth // 刷新浏览器的渲染队列机制,不用定时器
navDetail.style.transform = 'translateY(-10px)';
})animation
也可以不用
transform
head_box.css
.head-detail
同级定义动画1
2
3
4
5
6
7
8@keyframes headMove {
0% {
transform: translateY(-10px);
}
100% {
transform: translateY(0px);
}
}head_box.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24document.addEventListener('mouseover', function (ev) {
let target = ev.target, // 获取到事件源
ancestors = getAncestors(target)
// 对事件源判断,进行逻辑处理
// console.log(target) // 拿到的事件源,可能是head-nav里的任意子元素,所以要获取当前事件源所有的祖先元素
// 两次通过js获取到的dom元素对象,虽然也是堆内存,但两次获取的结果是相等的
// 事件源是头部导航
if(ancestors.indexOf(headNav) > -1) {
navDetail.style.display = 'block'
// navDetail.offsetWidth // 刷新浏览器的渲染队列机制,不用定时器
// navDetail.style.transform = 'translateY(0px)';
navDetail.style.animation = 'headMove .2s both'
return
}
// 事件源是详情
if(ancestors.indexOf(navDetail) > -1) return
// 其他事件源
navDetail.style.display = 'none'
// navDetail.offsetWidth // 刷新浏览器的渲染队列机制,不用定时器
// navDetail.style.transform = 'translateY(-10px)';
navDetail.style.animation = 'none'
})使用
animation
动画就不用处理浏览器渲染队列机制导致的问题了
背景按钮
查看详情是用的精灵图
水平居中,可以先在公共样式类预设类名
给对应的元素添加背景图、水平居中的类名,同时删除元素中的文字
common.less
1
2
3
4
5
6
7
8
9
10
11.center-x {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.center-y {
position: absolute;
top: 50%;
transform: translateY(-50%);
}head_box.less
1
2
3
4
5
6
7// 背景按钮
.background-btn {
bottom: 32px;
width: 160px;
height: 40px;
background-position: -216px -39px;
}index.html
1
2
3
4<!-- 背景按钮 -->
<a href="https://lol.qq.com/act/a20220715starguardianpass/index.html?_code=507041" target="_blank"
class="background-btn sp-top center-x">
</a>
加上作者区域的布局需求
能够同一行展示
标签整体靠右(文字整体靠右用
text-align
)想让标签既能够同行排列,也能够设置宽高,也能够当作文字做一些处理
基于以上需求,可以用
inline-block
布局方式index.html
1
2
3
4
5
6<!-- 作者区域 -->
<div class="author-box w1300">
<span>当前游戏版本</span>
<span>Ver 12.15</span>
<a href="https://lol.qq.com/gicp/news/410/37008396.html" target="_blank" class="author-link">版本详情</a>
</div>head_box.less
和
backgroun-btn
同级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// 作者区域
.author-box {
margin-top: 223px;
text-align: right;
//font-size: 0;
span, .author-link {
display: inline-block;
margin: 0 5px;
vertical-align: middle;
}
span {
color: #888787;
font-size: 14px;
&:nth-of-type(2) {
color: #f5d185;
}
}
.author-link {
padding: 0 15px;
height: 22px;
line-height: 20px;
color: #f5d185;
font-size: 12px;
border: 1px solid #f5d185;
background: rgba(0,0,0,0.7);
}
}效果:
按钮组
- 箭头动画去查
animate.css
动画库
common.less
1 | .sp-comm { |
index.html
1 | <!-- 按钮组 --> |
index.js
1 | import './head_box.js'; |
head_box.less
1 | // head-search |
效果:
头部导航小结
hover
交互效果:- 同级结构的可以通过事件委托,给祖先元素绑定鼠标移入移出事件,动态修改样式及动画
- 嵌套结构,直接可以通过
css
的hover
效果实现
其他动画效果:
- 借助
animate.css
动画库,找到满足动画效果的css
样式
- 借助
需要补足的点(能看懂,但写不出来,
dom
操作练习的少):js
动态操作样式js
操作dom
、获取类名等相关dom
操作
通用流程
- 先准备好物料:资源背景图、尺寸图等
- 搭建结构,
- 在搭建结构的时候,用什么标签心里已经有数
- 同时,这一块的布局,字体相关样式,应该也已了然于心
- 最好能够在搭结构的时候,就已经想好交互的解决方案
- 对于抽取的公共模块做到心中有数
常见组合拳
字体相关
1
2
3
4
5
6
7
8
9
10font-size
color
lineheight
letter-spacing: 1px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center; // 父级
vertical-align: middle; // 父级
轮播图+页卡静态页面
根据上面的流程,将首页静态页面写完
轮播+新闻模块静态页面
common.less
1 | body { |
index.html
1 | <!--banner-and-news--> |
index.less
1 | @import './banner_and_box.less'; |
banner_and_box.less
1 | .banner-and-news { |
注意:
head-box
加个下面的样式
1 | min-width: 1300px; |
保证缩小窗口时,上下两栏能够始终对齐
最终效果:
轮播图模块
轮播图数据获取
轮播图的数据,需要动态获取,然后动态绑定到页面中
动态绑定之后,然后再实现轮播效果
数据不直接写死,后台数据在admin
文件夹(为了练习到axios
)
Node
版本>12.0.0
- 启动:
node server.js
,注意不能关窗口,如果想要关闭窗口服务还在,安装npm i pm2
,启动pm2 start server.js --name lol
,结束pm2 stop lol
,重启pm2 start lol
- 可以学习一下,
mock
数据怎么简单搭建成api
服务
想从服务器获得数据(前后端数据通信)
- 同源方案
ajax
、$ajax
基于回调函数、axios
基于Promise
,核心是XMLRequest
fetch
,ES6
中提供的新的方法
- 跨域方案
jsonp
postmessage
http.js
1 | const http = axios.create({ |
新建swiper_container_home.js
,index.js
中导入
index.js
1 | import './head_box.js'; |
swiper_container_home.js
1 | import http from './http.js' |
结果:
录播图和分页器的结构,只留一个。并且是根据服务器返回的数据动态创建的
swiper_container_home.js
1 | import http from './http.js' |
修改对应结构,index.html
1 | <!--轮播图开始--> |
修改对应样式,banner_and_box.less
1 | .swiper-wrapper { |
增加图片加载时的loading
效果
- 可以是个圈圈(
loading
) - 也可以是个架子(骨架屏)
- 也可以做百分比进度条(监听
ajax
)- 一般只有大文件上传的时候会处理
common.less
1 | /* loading */ |
给轮播图增加loading
样式类,并适当调整盒子的布局
index.html
1 | <!--轮播图开始--> |
banner_and_box.less
,主要是使得盒子恢复到原来的位置
1 | .banner-and-news { |
数据绑定后,移出loading
样式类
swiper_container_home.js
1 | http.get('/home_banner').then(data => { |
轮播图实现原理
调整banner-box
相关样式
1 | .banner-box { |
效果,这里暂时没有设置overflow
为hidden
需要隐藏其他图片的话,还要将swiper-wrapper
的父元素宽度改成和图片一样
需要展示的图片排列成一行,定时控制每一张图片展示在视口中
图片相对于与视口定位(调整
left
值)或使用transform
调整translateX
值- 整个外部容器绝对定位,图片
wrapper
相对定位,且每张图片水平排列- 外部的图片容器,
position: absolute
,再设置外图片容器的父元素position:relative
- 图片容器定位的初始值:
left:0;top:0;
- 给图片容器一个过渡的动画效果:
transition:left .3s;
- 外部的图片容器,
- 容器是溢出隐藏的,只会看到一张
- 整个外部容器绝对定位,图片
实现无缝衔接
复制第一张放在末尾,获取到
data
数据后,单独再拼接一次data[0]
1
2
3
4
5
6
7
8
9
10
11
12let first = data[0]
// 为了实现无缝衔接,拼好后再放一张图片,克隆的部分,只克隆图片
if (first) {
str_slide += `<a href="${first.href}" targe="_blank">
<img src="${first.pic}" class="banner-img" alt="${first.title}">
</a>`
}
// 拼好后放入指定容器中
wrapper.innerHTML = str_slide
pagenation.innerHTML = str_pagenation多克隆一份之后,动态计算
wrapper
的宽度- 定义
count
记录每个slice
图片的数量,包含克隆的哪一张 - 主逻辑中,移除加载动画类后,计算
count
,值为data.length+1
,可以进行边界判断,但一般来说,服务器只要返回了数据,data.length
都是不为空的 - 有了图片的总数量,
wrapper
的宽度就可以进行设置了:wrapper.style.width
- 可以直接写死
- 也可以动态获取,先获取每张图片的宽度:
slideWidth = swiperContainerHome.offsetWidth,
,再乘以数量:${count*slideWidth}px
- 同时计算每次左移的值
wrapper.style.left
:${-step*slideWidth}px
- 定义
需要计算的值,获取到后,在主逻辑中,开启轮播图
- 定义运动时间
interval
、定时器对象autoTimer
、轮播函数autoMove
- 轮播函数
autoMove
- 自增步长
step
,记录图片位置 - 判断步长和图片总数的关系,如果走到了末尾,即
step >= count
,让wrapper
立即回到第一张,即修改left
样式为0,并设置这一次的过渡动画的时间为0(注意获取下wrapper
的属性,刷新下浏览器的渲染队列)- 最后步长归为重置为1
- 如果没有到末尾,每次修改
wrapper
的left
值并添加过渡时间为.3s
- 每次切换完,实现分页器对齐
- 自增步长
- 定义运动时间
到末尾后,立即调到第一张(视差感受不到),然后继续轮播第二张
- 通过设置
left
实现移动- 第一张的
left
值为0 - 每过3秒钟,
step
步长加一,第二张的left
值为-780px
- 第三张的索引为2,
left
值为-780*2px
- 第一张的
- 每次调用轮播的函数,记录
step
,通过step
和图片总长度的比较来了解图片的位置
- 通过设置
整个
wrapper
的长度,为图片数量加一,乘以每张图片的长度
主逻辑中:当鼠标滑入
wrapper-container-home
时,结束自动轮播;鼠标离开,开启自动轮播- 注意实际的
dom
范围
- 注意实际的
实现分页器对齐
定义分页器对齐函数
pagenationAlign
,每次轮播切换结束的最后,调用分页器对齐函数根据
step
的值,给对应的span
添加active
样式类- 定义
pagenationList
为数组,数据绑定之后,使用Array.from
对querySelectorAll
获取span
得到类数组转为数组(定义在开启轮播图之前) - 我们这里是用
ul
写的,将获取span
修改成获取li
- 定义
分页器对齐函数
- 拿到
step
后,存到temp
临时变量中(分页器的索引,比图片的索引少一项,图片0和图片6应该都是对应分页器0) - 比较
temp
和count-1
,如果相等了,表示拿到了最大索引,对应的应该是克隆的那张图片,分页器的索引应该是0 - 循环分页器数组,比较
index
和temp
动态添加active
样式类
- 拿到
实现分页器切换
- 鼠标划过每个分页器的时候,也能够控制分页器切换
- 有两种方案
- 获取到所有的分页器,循环,每次都添加事件绑定。这种方式会产生多个闭包,性能也不太好
- 使用事件委托,注意
mouseenter
是没有冒泡传播机制的,要使用mouseover
- 基于自定义属性,获取每个分页器的索引
swiper_container_home.js
1 | import http from './http.js' |
小结
swiper.js
源码不难,只是处理了更多的可能性,没有特别值得学习的代码思想pc
端的轮播图真用不上swiper
,就这点代码移动端涉及到
touch
,自己写也可以,但有点麻烦而已上面的真没有什么难度,闭着眼睛都能写出来,真正难的是自己能够封装成一个开源级的插件。用
swiper
不是不可以,但它封装的东西太多了,整个项目中,就只用到了类似上面的轮播图,用swier
反而得不偿失。这时候就需要自己,能够将轮播图功能封装成插件,自己写的插件,压缩完之后也就几
kb
大小,也支持数据绑定,功能也能实现。所以并不是所有的功能,能有第三方插件解决,就一定得用第三方插件。
- 小公司里拿插件快速解决没问题
- 大公司要追求极致性能体验,就需要自己能够实现插件封装
- 要考虑哪些维度
- 怎么实现可扩容性
- 容错处理,让风险评估达到最低
- 优化、面向对象的一些东西
插件封装是
JS
高级课程里的- 可以学习优秀的开源框架:
jQuery、Axios、PromiseA+
- 学完优秀开源框架源码,目的是掌握背后的设计思想,自己也能造轮子。
- 而封装轮播图插件,是自己造轮子的开胃菜,那个时候的代码就不是现在这些破玩意儿了
- 可以学习优秀的开源框架:
页卡切换
- 鼠标划过数据能够切换
- 有的是划过后,数据会重新加载
页卡切换常见的三大方案
- 从服务器端获取所有的页卡内容,动态绑定到每一个内容区域中
- 刚开始只展示第一个页卡及对应的内容(选中态),剩下的已经绑定好了,没有展示而已
- 鼠标滑到第二个页卡,移出第一个页卡的选中态(通过控制选中态,实现页卡的显示隐藏)
- 内容区域只有一个,滑到哪个页卡就展示哪部分的内容区域
- 一次性获取所有页卡内容,滑过哪个页面,在内容中找出对应内容
- 或者是,内容区域只有一个,刚开始只获取一开始要展示的内容,切换页卡时,重新从服务器获取内容
页卡一
获取全部数据,控制选中态
- 给新闻模块,添加
id
- 新建
news_box.js
模块- 获取服务器数据
- 实现数据绑定:动态拼接
html
字符- 先整体评估下,获取到所有接下来要操作的元素
- 注释掉静态页面中,需要动态生成的结构;给新闻部分添加
loading
样式类- 结构可能需要重写,注意服务器返回的数据
- 实现鼠标划过时,控制每部分的显示
- 循环菜单栏,当前选中的有样式类,其他未选中的则移除样式类
- 同步菜单栏和新闻栏
news_box.js
1 | import http from './http.js' |
课程结束,剩余部分自己实现