PC端网页特效
1.元素偏移量 offset 系列
1.1 offset 概述
offset 翻译过来就是偏移量,我们使用offset系列相关属性可以动态
的得到该元素位置(偏移) 、大小等
- 获得元素距离带有定位父元素的位置
- 获得元素自身的大小(宽度高度)
- 注意:返回的数值都不带单位
offset 系列常用属性:
offset系列属性 | 作用 |
---|---|
element.offsetParent | 返回作为该元素带有定位的父级元素 如果父级都没有定位则返回body |
element.offsetTop | 返回元素相对带有定位父元素上方的偏移 |
element.offsetLeft | 返回元素相对带有定位父元素左边框的偏移 |
element.offsetWidth | 返回自身包括padding、边框、内容区的宽度,返回数值不带单位 |
elelment.offsetHeight | 返回自身包括padding、边框、内容区的高度,返回数值不带单位 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>offset系列属性</title>
<style>
* {
padding: 0;
margin: 0;
}
.father {
position: relative;
width: 300px;
height: 300px;
background-color: pink;
margin: 200px 100px;
}
.son {
width: 150px;
height: 150px;
background-color: purple;
margin-left: 45px;
}
.w {
/* width: 200px; */
height: 200px;
background-color: skyblue;
margin: 0 auto 200px;
padding: 10px;
border: 15px solid red;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<div class="w"></div>
<script>
// offset 系列
var father = document.querySelector('.father');
var son = document.querySelector('.son');
var w = document.querySelector('.w');
// 1. 可以得到元素的偏移 位置 返回的不带单位的数值
console.log(father.offsetTop);
console.log(father.offsetLeft);
// 它以带有定位的父元素为准 如果没有父元素或者父元素没有定位 则以 body 为准
console.log(son.offsetLeft);
// 2. 可以得到元素的大小 宽度和高度 是包含padding + border + width
console.log(w.offsetWidth);
console.log(w.offsetHeight);
// 3. 返回带有定位的父元素 否则的是body
console.log(son.offsetParent);
console.log(son.parentNode); // 返回父元素 是最近一级父元素 不管有没有定位
</script>
</body>
</html>
1.2 offset与style区别
offset
- offset可以得到任意样式表中的样式值
- offset系列获得的数值是没有单位的
- offsetWidth包含padding+border+width
- offsetWidth等属性是只读属性,只能获取不能赋值
所以,我们想要获取元素大小位置,有offset更合适
style
- style只能得到行内样式表中的样式
- style.width获得的是带有单位的字符串
- style.width获得不包含padding和border的值
- style.width是可读写属性,可以获取也可以赋值
所以,我们想要给元素更改值,则需要用style改变
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>offset和style的区别</title>
<style>
.box {
width: 200px;
height: 200px;
background-color: pink;
padding: 10px;
}
</style>
</head>
<body>
<div class="box" style="width: 200px;"></div>
<script>
var box = document.querySelector('.box');
console.log(box.offsetWidth);
console.log(box.style.width);
</script>
</body>
</html>
案例:获取鼠标在盒子内的坐标
案例分析
- 我们在盒子内点击,想要得到鼠标距离盒子左右的距离。
- 首先得到鼠标在页面中的坐标(e.pageX, e.pageY)
- 其次得到盒子在页面中的距离(box.offsetLeft,box.offsetTop)
- 用鼠标距离页面的坐标减去盒子在页面中的距离,得到鼠标在盒子内的坐标
- 如果想要移动柜一下鼠标,就要获取最新的坐标,使用鼠标移动事件mousemove
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>计算鼠标在盒子内的坐标</title>
<style>
.box {
width: 300px;
height: 300px;
background-color: pink;
margin: 200px 200px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
var box = document.querySelector('.box');
box.addEventListener('mousemove', function(e) {
// console.log(e.pageX);
// console.log(e.pageY);
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
this.innerHTML = 'x坐标是' + x + ' y坐标是' + y;
})
</script>
</body>
</html>
案例:模态框拖拽
弹出框,我们也称为模态框
- 点击弹出层,会弹出模态框,并且显示灰色半透明的遮挡层。
- 点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮罩层。
- 鼠标放到模态层最上面一行,可以按住鼠标拖拽模态框在页面中移动。
- 鼠标松开,可以停止拖动模态框移动。
案例分析
- 点击弹出层,模态层和遮罩层就会显示出来display:block;
- 点击关闭按钮,模态框和遮罩层就会隐藏起来display:none;
- 在页面中拖拽的原理:鼠标按下并且移动,之后松开鼠标
- 触发事件是鼠标按下mousedown,鼠标移动mousemove鼠标松开mouseup
- 拖拽过程:鼠标移动过程中,获得最新的值赋值给模态框的lef和top值,这样模态框就可以跟着鼠标走了
- 鼠标按下触发的事件源是最上面一行,就是id为title
- 鼠标的坐标减去鼠标在盒子内的坐标,才是模态框真正的位置。
鼠标按下
,我们要得到鼠标在盒子的坐标。鼠标移动
,就让模态框的坐标设置为:鼠标坐标减去盒子坐标即可,注意移动事件写到按下事件里面。鼠标松开
,就停止拖拽,就是可以让鼠标移动事件解除
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模态框拖拽</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.login-header {
width: 100%;
text-align: center;
font-size: 24px;
margin-top: 15px;
}
input {
border: 0;
}
a {
color: #000;
text-decoration: none;
}
.login {
display: none;
width: 512px;
height: 280px;
position: fixed;
border: #ebebeb solid 1px;
left: 50%;
top: 50%;
background: #ffffff;
box-shadow: 0px 0px 20px #ddd;
z-index: 9999;
transform: translate(-50%, -50%);
text-align: center;
}
.login-title {
text-align: center;
font-size: 20px;
margin: 15px 0 20px 0;
}
.login-title:hover {
cursor: move;
}
.login-title span {
position: absolute;
top: -24px;
right: -24px;
width: 48px;
height: 48px;
line-height: 45px;
text-align: center;
font-size: 14px;
background-color: #fff;
border-radius: 50%;
border: 1px solid #ccc;
}
.login-input {
overflow: hidden;
}
.list-input {
float: left;
width: 350px;
height: 35px;
line-height: 35px;
border: 1px solid #ebebeb;
margin-bottom: 20px;
text-indent: 6px;
}
.login-input label {
float: left;
width: 90px;
padding-right: 10px;
text-align: right;
line-height: 35px;
height: 35px;
font-size: 14px;
}
.login-button {
width: 50%;
height: 42px;
line-height: 42px;
margin: 15px auto 0px;
font-size: 14px;
border: 1px solid #ebebeb;
}
.login-button a {
display: block;
}
.login-bg {
display: none;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, .3);
}
</style>
</head>
<body>
<div class="login-header"><a href="javascript:;" id="link">点击,弹出登录框</a></div>
<div class="login" id="login">
<div class="login-title" id="title">登录会员
<span><a href="javascript:void(0);" id="closeBtn" class="close-login">关闭</a></span>
</div>
<div class="login-input-content">
<div class="login-input">
<label for="">用户名: </label>
<input type="text" id="username" class="list-input" placeholder="请输入用户名" name="info[username]">
</div>
<div class="login-input">
<label for="">登录密码: </label>
<input type="password" name="info[password]" id="password" class="list-input" placeholder="请输入登录密码">
</div>
</div>
<div class="login-button" id="loginBtn"><a href="javascript:void(0);" id="login-button-submit">登录会员</a></div>
</div>
<!-- 遮盖层 -->
<div class="login-bg" id="bg"></div>
<script>
// 1. 获取元素
var login = document.querySelector('.login');
var mask = document.querySelector('.login-bg');
var link = document.querySelector('#link');
var closeBtn = document.querySelector('#closeBtn');
var title = document.querySelector('#title');
// 2. 点击弹出层这个链接 link 让mask和login显示出来
link.addEventListener('click', function() {
mask.style.display = 'block';
login.style.display = 'block';
})
// 3. 点击closeBtn 让mask和login隐藏
closeBtn.addEventListener('click', function() {
mask.style.display = 'none';
login.style.display = 'none';
})
// 4. 开始拖拽
// (1) 当我们鼠标按下,就会获得鼠标在盒子内的坐标
title.addEventListener('mousedown', function(e) {
var x = e.pageX - login.offsetLeft;
var y = e.pageY - login.offsetTop;
// (2) 鼠标移动的时候,把鼠标在页面中的坐标减去鼠标在盒子内的坐标就是模态框的left和top值
document.addEventListener('mousemove', move)
function move(e) {
login.style.left = e.pageX - x + 'px';
login.style.top = e.pageY - y + 'px';
}
// (3) 鼠标弹起就让鼠标移动事件移除
document.addEventListener('mouseup', function() {
document.removeEventListener('mousemove', move);
})
})
</script>
</body>
</html>
案例:仿京东放大镜
案例分析
- 整个案例可以分为三个功能模块
鼠标经过小图片盒子,黄色的遮挡层和大图片显示,离开隐藏2个盒子功能
- 就是显示与隐藏
黄色的遮挡层跟随鼠标功能
把鼠标坐标给遮挡层不合适。因为遮挡层坐标以父盒子为准。
- 首先是获得鼠标在盒子的坐标
- 之后把数值给遮挡层作为left和top值
- 此时用到鼠标移动事件,但是还是在小图片盒子内移动
- 发现遮挡层位置不对,需要再减去盒子自身高度和宽度的一半。
- 遮挡层不能超出小图片盒子范围
- 如果小于零,就把坐标设置为0
- 遮挡层最大移动距离:小图片盒子宽度减去遮挡层盒子宽度
移动黄色遮挡层,大图片跟随移动功能
求大图片的移动距离公式
- 遮挡层移动距离/遮挡层最大移动距离 = 大图片移动距离/大图片最大移动距离
- 大图片移动距离 = 遮挡层移动距离*大图片最大移动距离/遮挡层最大移动距离
<div class="product_intro clearfix">
<!-- 预览区域 -->
<div class="preview_wrap fl">
<div class="preview_img">
<img src="upload/s3.png" alt="">
<div class="mask"></div>
<div class="big">
<img src="upload/big.jpg" alt="" class="bigImg">
</div>
</div>
<div class="preview_list">
<a href="#" class="arrow_prev"></a>
<a href="#" class="arrow_next"></a>
<ul class="list_item">
<li>
<img src="upload/pre.jpg" alt="">
</li>
<li class="current">
<img src="upload/pre.jpg" alt="">
</li>
<li>
<img src="upload/pre.jpg" alt="">
</li>
<li>
<img src="upload/pre.jpg" alt="">
</li>
<li>
<img src="upload/pre.jpg" alt="">
</li>
</ul>
</div>
</div>
<!-- 产品详细信息 -->
<div class="itemInfo_wrap fr">
<div class="sku_name">
Apple iPhone 6s(A1700)64G玫瑰金色 移动通信电信4G手机
</div>
<div class="news">
推荐选择下方[移动优惠购],手机套餐齐搞定,不用换号,每月还有花费返
</div>
<div class="summary">
<dl class="summary_price">
<dt>价格</dt>
<dd>
<i class="price">¥5299.00 </i>
<a href="#">降价通知</a>
<div class="remark">累计评价612188</div>
</dd>
</dl>
<dl class="summary_promotion">
<dt>促销</dt>
<dd>
<em>加购价</em> 满999.00另加20.00元,或满1999.00另加30.00元,或满2999.00另加40.00元,即可在购物车换 购热销商品 详情 》
</dd>
</dl>
<dl class="summary_support">
<dt>支持</dt>
<dd>以旧换新,闲置手机回收 4G套餐超值抢 礼品购</dd>
</dl>
<dl class="choose_color">
<dt>选择颜色</dt>
<dd>
<a href="javascript:;" class="current">玫瑰金</a>
<a href="javascript:;">金色</a>
<a href="javascript:;">白色</a>
<a href="javascript:;">土豪色</a>
</dd>
</dl>
<dl class="choose_version">
<dt>选择版本</dt>
<dd>
<a href="javascript:;" class="current">公开版</a>
<a href="javascript:;">移动4G</a>
</dd>
</dl>
<dl class="choose_type">
<dt>购买方式</dt>
<dd>
<a href="javascript:;" class="current">官方标配</a>
<a href="javascript:;">移动优惠购</a>
<a href="javascript:;">电信优惠购</a>
</dd>
</dl>
<div class="choose_btns">
<div class="choose_amount">
<input type="text" value="1">
<a href="javascript:;" class="add">+</a>
<a href="javascript:;" class="reduce">-</a>
</div>
<a href="#" class="addcar">加入购物车</a>
</div>
</div>
</div>
</div>
/*详情页的样式文件*/
.de_container {
margin-top: 20px;
}
.crumb_wrap {
height: 25px;
}
.crumb_wrap a {
margin-right: 10px;
}
.preview_wrap {
width: 400px;
height: 590px;
}
.preview_img {
position: relative;
height: 398px;
border: 1px solid #ccc;
}
.mask {
display: none;
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 300px;
background: #FEDE4F;
opacity: .5;
border: 1px solid #ccc;
cursor: move;
}
.big {
display: none;
position: absolute;
left: 410px;
top: 0;
width: 500px;
height: 500px;
background-color: pink;
z-index: 999;
border: 1px solid #ccc;
overflow: hidden;
}
.big img {
position: absolute;
top: 0;
left: 0;
}
.preview_list {
position: relative;
height: 60px;
margin-top: 60px;
}
.list_item {
width: 320px;
height: 60px;
margin: 0 auto;
}
.list_item li {
float: left;
width: 56px;
height: 56px;
border: 2px solid transparent;
margin: 0 2px;
}
.list_item li.current {
border-color: #c81623;
}
.arrow_prev, .arrow_next {
position: absolute;
top: 15px;
width: 22px;
height: 32px;
background-color: purple;
}
.arrow_prev {
left: 0;
background: url(../img/arrow-prev.png) no-repeat;
}
.arrow_next {
right: 0;
background: url(../img/arrow-next.png) no-repeat;
}
.itemInfo_wrap {
width: 718px;
}
.sku_name {
height: 30px;
font-size: 16px;
font-weight: 700;
}
.news {
height: 32px;
color: #e12228;
}
.summary dl {
overflow: hidden;
}
.summary dt, .summary dd {
float: left;
}
.summary dt {
width: 60px;
padding-left: 10px;
line-height: 36px;
}
.summary_price, .summary_promotion {
position: relative;
padding: 10px 0;
background-color: #fee9eb;
}
.price {
font-size: 24px;
color: #e12228;
}
.summary_price a {
color: #c81623;
}
.remark {
position: absolute;
right: 10px;
top: 20px;
}
.summary_promotion {
padding-top: 0;
}
.summary_promotion dd {
width: 450px;
line-height: 36px;
}
.summary_promotion em {
display: inline-block;
width: 40px;
height: 22px;
background-color: #c81623;
text-align: center;
line-height: 22px;
color: #fff;
}
.summary_support dd {
line-height: 36px;
}
.choose_color a {
display: inline-block;
width: 80px;
height: 41px;
background-color: #f7f7f7;
border: 1px solid #ededed;
text-align: center;
line-height: 41px;
}
.summary a.current {
border-color: #c81623;
}
.choose_version {
margin: 10px 0;
}
.choose_version a, .choose_type a {
display: inline-block;
height: 32px;
padding: 0 12px;
background-color: #f7f7f7;
border: 1px solid #ededed;
text-align: center;
line-height: 32px;
}
.choose_btns {
margin-top: 20px;
}
.choose_amount {
position: relative;
float: left;
width: 50px;
height: 46px;
background-color: pink;
}
.choose_amount input {
width: 33px;
height: 44px;
border: 1px solid #ccc;
text-align: center;
}
.add, .reduce {
position: absolute;
right: 0;
width: 15px;
height: 22px;
border: 1px solid #ccc;
background-color: #f1f1f1;
text-align: center;
line-height: 22px;
}
.add {
top: 0;
}
.reduce {
bottom: 0;
/*禁止鼠标样式*/
cursor: not-allowed;
/* pointer 小手 move 移动 */
}
.addcar {
float: left;
width: 142px;
height: 46px;
background-color: #c81623;
text-align: center;
line-height: 46px;
font-size: 18px;
color: #fff;
margin-left: 10px;
font-weight: 700;
}
.product_detail {
margin-bottom: 50px;
}
.aside {
width: 208px;
border: 1px solid #ccc;
}
.tab_list {
overflow: hidden;
height: 34px;
}
/*把背景颜色 底边框都给 li*/
.tab_list li {
float: left;
background-color: #f1f1f1;
border-bottom: 1px solid #ccc;
height: 33px;
text-align: center;
line-height: 33px;
}
/*鼠标单击 li 变化样式 背景变白色 去掉下边框 文字变颜色*/
.tab_list .current {
background-color: #fff;
border-bottom: 0;
color: red;
}
.first_tab {
width: 104px;
}
.second_tab {
width: 103px;
border-left: 1px solid #ccc;
}
.tab_con {
padding: 0 10px;
}
.tab_con li {
border-bottom: 1px solid #ccc;
}
.tab_con li h5 {
/*超出的文字省略号显示*/
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 400;
}
.aside_price {
font-weight: 700;
margin: 10px 0;
}
.as_addcar {
display: block;
width: 88px;
height: 26px;
border: 1px solid #ccc;
background-color: #f7f7f7;
margin: 10px auto;
text-align: center;
line-height: 26px;
}
.detail {
width: 978px;
}
.detail_tab_list {
height: 39px;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
.detail_tab_list li {
float: left;
height: 39px;
line-height: 39px;
padding: 0 20px;
text-align: center;
cursor: pointer;
}
.detail_tab_list .current {
background-color: #c81623;
color: #fff;
}
.item_info {
padding: 20px 0 0 20px;
}
.item_info li {
line-height: 22px;
}
.more {
float: right;
font-weight: 700;
font-family: 'icomoon';
}
window.addEventListener('load', function() {
var preview_img = document.querySelector('.preview_img');
var mask = document.querySelector('.mask');
var big = document.querySelector('.big');
// 1. 当鼠标经过 preview_img 就显示和隐藏 mask遮挡层和big大盒子
preview_img.addEventListener('mouseover', function() {
mask.style.display = 'block';
big.style.display = 'block';
})
preview_img.addEventListener('mouseout', function() {
mask.style.display = 'none';
big.style.display = 'none';
})
preview_img.addEventListener('mousemove', function(e) {
// (1). 先计算出鼠标在盒子内的坐标
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
// console.log(x, y);
// (2) 减去盒子高度300的一半是150 就是我们mask最终的left和top值
// (3) mask 移动的距离
var maskX = x - mask.offsetWidth / 2;
var maskY = y - mask.offsetHeight / 2;
var moveWidthX = preview_img.offsetWidth - mask.offsetWidth;
var moveWidthY = preview_img.offsetHeight - mask.offsetHeight;
// 如果x坐标小于了0,就让他停在0的位置
if (maskX <= 0) {
maskX = 0;
} else if (maskX >= moveWidthX) {
maskX = moveWidthX;
}
if (maskY <= 0) {
maskY = 0;
} else if (maskY >= moveWidthY) {
maskY = moveWidthY;
}
mask.style.left = maskX + 'px';
mask.style.top = maskY + 'px';
// 大图片移动距离 = 遮挡层移动距离*大图片最大移动距离/遮挡层最大移动距离
// 大图
var bigImg = document.querySelector('.bigImg');
// 大图片最大移动距离
var bigMax = bigImg.offsetWidth - big.offsetWidth;
// 大图片移动距离 X Y
var bigX = maskX * bigMax / moveWidthX;
var bigY = maskY * bigMax / moveWidthX;
bigImg.style.left = -bigX + 'px';
bigImg.style.top = -bigY + 'px';
})
})
1.3 offsetX,offsetLeft,offsetWidth的区别
offsetX/offsetY
offsetX和offsetY表示(鼠标位置)相对于最近父级元素的坐标(无论父级是否定位)(不管是谁触发)
.big{
width: 200px;
height: 200px;
border: 1px solid red;
position: absolute;
top: 100px;
left: 100px;
}
.box{
width: 100px;
height: 100px;
border: 1px solid green;
margin-left: 50px;
}
<div class="big">
<div class="box"></div>
</div>
var box = document.getElementsByClassName('box')[0];
var big = document.getElementsByClassName('big')[0];
big.onclick = function(e){
e = e || window.event;
console.log(e.offsetX);//若点击位置在box里面则返回的数据是鼠标相对于box的坐标,若点击位置在big里面则返回的值是鼠标相对于big的坐标,和是谁触发事件的无关。
}
offsetLeft/offsetTop
元素相对于最近定位父级元素的坐标,若在所有的父级上都没有定位,则相对于整个文档
.big{
width: 200px;
height: 200px;
border: 1px solid red;
/* position: absolute; */
}
.small{
width: 100px;
height: 100px;
border: 1px solid green;
}
<div class="big">
<div class="small"></div>
</div>
var small = document.getElementsByClassName('small')[0];
var big = document.getElementsByClassName('big')[0];
console.log(small.offsetLeft); //不加position结果是9,加上position结果是0
offsetWidth/offsetHeight
返回元素的视觉尺寸(width+padding+border)
.big{
width: 200px;
height: 200px;
border: 1px solid red;
position: absolute;
top: 100px;
left: 100px;
}
.box{
width: 100px;
height: 100px;
border: 1px solid green;
margin-left: 50px;
}
<div class="big">
<div class="box"></div>
</div>
var box = document.getElementsByClassName('box')[0];
var big = document.getElementsByClassName('big')[0];
console.log(big.offsetWidth);//202
console.log(box.offsetWidth);//102
2.元素可视区 client系列
client
翻译过来就是客户端,我们使用client系列的相关属性来获取元素可视区的相关信息。通过client系列的相关属性可以动态的得到该元素的边框大小、元素大小等。
client系列属性 | 作用 |
---|---|
element.clientTop | 返回元素上边框的大小 |
element.clientLeft | 返回元素左边框的大小 |
element.clientWidth | 返回自身包括padding、内容区的宽度,不含边框,返回数值不带单位 |
element.clientHeight | 返回自身包括padding、内容区的高度,不包边框,返回数值不带单位 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>client系列</title>
<style>
div {
width: 200px;
height: 200px;
background-color: pink;
border: 10px solid red;
padding: 10px;
}
</style>
</head>
<body>
<div></div>
<script>
// client 宽度 和 offsetWidth 最大的区别就是 不包含边框
var div = document.querySelector('div');
console.log(div.clientWidth);
</script>
</body>
</html>
案例:淘宝flexible.js源码分析
立即执行函数(function(){})() 或者 (function() {}())
主要作用:创建一个独立的作用域。
下面三种情况都会刷新页面都会触发load事件。
- a标签的超链接
- F5或者刷新按钮(强制刷新)
- 前进后退按钮
但是火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了页面里。
所以此时后退按钮不能刷新页面。
此时可以使用pageshow事件来触发。这个事件在页面显示时触发,无论页面是否来自缓存。在重新加载页面中。pageshow会在load事件触发后触发;根据事件对象中的persisted来判断是否是缓存中的页面触发的pageshow事件,注意这个事件给window添加。
(function flexible(window, document) {
// 获取的html 的根元素
var docEl = document.documentElement
// dpr 物理像素比
var dpr = window.devicePixelRatio || 1
// adjust body font size 设置我们body 的字体大小
function setBodyFontSize() {
// 如果页面中有body 这个元素 就设置body的字体大小
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
} else {
// 如果页面中没有body 这个元素,则等着 我们页面主要的DOM元素加载完毕再去设置body
// 的字体大小
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
// set 1rem = viewWidth / 10 设置我们html 元素的文字大小
function setRemUnit() {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
// reset rem unit on page resize 当我们页面尺寸大小发生变化的时候,要重新设置下rem 的大小
window.addEventListener('resize', setRemUnit)
// pageshow 是我们重新加载页面触发的事件
window.addEventListener('pageshow', function(e) {
// e.persisted 返回的是true 就是说如果这个页面是从缓存取过来的页面,也需要从新计算一下rem 的大小
if (e.persisted) {
setRemUnit()
}
})
// detect 0.5px supports 有些移动端的浏览器不支持0.5像素的写法
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
}(window, document))
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>像素比和pageshow事件</title>
<script src="07-flexible分析.js"></script>
</head>
<body>
<script>
console.log(window.devicePixelRatio);
window.addEventListener('load', function () {
alert(11);
})
</script>
<a href="http://www.itcast.cn">链接</a>
</body>
</html>
3.元素滚动 scroll系列
3.1 元素scroll系列属性
scroll
翻译过来就是滚动的,我们使用scroll系列的相关属性可以动态的得到该元素的大小、滚动距离等。
scroll系列属性 | 作用 |
---|---|
element.scrollTop | 返回被卷去的上侧距离,返回数值不带单位 |
element.scrollLeft | 返回被卷去的左侧距离,返回数值不带单位 |
element.scrollWidth | 返回自身实际的宽度,不含边框,返回数值不带单位 |
element.scrollHeight | 返回自身实际的高度,不含边框,返回数值不带单位 |
3.2 页面被卷去的头部
如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发onscroll事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>scroll系列</title>
<style>
div {
width: 200px;
height: 200px;
background-color: pink;
border: 10px solid red;
padding: 10px;
overflow: auto;
/* display: -webkit-box;
-webkit-line-clamp: 10;
-webkit-box-orient: vertical;
text-overflow: ellipsis; */
}
</style>
</head>
<body>
<div>
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
我是内容
</div>
<script>
// scroll系列
var div = document.querySelector('div');
console.log(div.scrollHeight);
console.log(div.clientHeight);
// scroll滚动事件当滚动条发生变化的时候会触发的事件
div.addEventListener('scroll', function() {
console.log(div.scrollTop);
})
</script>
</body>
</html>
案例:仿淘宝固定右侧侧边栏
- 原先侧边栏是绝对定位
- 当页面滚动到一定位置,侧边栏改为固定定位
- 页面继续滚动,会让返回顶部显示出来
案例分析
- 需要用到页面滚动事件scroll因为是页面滚动,所以事件源是document
- 滚动到某个位置,就是判断页面被卷去的上部值
页面被卷去的头部:可以通过window.pageYOffset 获得
如果是被卷去的左侧 window.pageXOffset- 注意,元素被卷去的头部是
element.scrollTop
,如果是页面被卷去的头部则是window.pageXOffset
需要注意的是,页面被卷去的头部,有兼容性问题,因此被卷去的头部通常有如下几种写法:
- 声明了DTD使用document.documentElement.scrollTop
- 未声明DTD,使用 document.body.scrollTop
- 新方法window.pageYOffset 和 window.pageXOffset, IE9开始支持
function() {
return {
left: window.pageXOffset || documentElement.scrollLeft || document.body.scrollLeft || 0,
top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
};
}
// 使用的时候 getScroll().left
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>仿淘宝固定侧边栏</title><style>
.slider-bar {
position: absolute;
left: 50%;
top: 300px;
margin-left: 600px;
width: 45px;
height: 130px;
background-color: pink;
}
.w {
width: 1200px;
margin: 10px auto;
}
.header {
height: 150px;
background-color: purple;
}
.banner {
height: 250px;
background-color: skyblue;
}
.main {
height: 1000px;
background-color: yellowgreen;
}
span {
display: none;
position: absolute;
bottom: 0;
}
</style>
</head>
<body>
<div class="slider-bar">
<span class="goBack">返回顶部</span>
</div>
<div class="header w">头部区域</div>
<div class="banner w">banner区域</div>
<div class="main w">主体部分</div>
<script>
// 1. 获取元素
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
// 获取 main 主体元素
var main = document.querySelector('.main');
var goBack = document.querySelector('.goBack');
var mainTop = main.offsetTop;
// banner.offsetTop 就是被卷去的头部的大小 一定要写到滚动事件的外面
var bannerTop = banner.offsetTop;
// 当我们侧边栏固定定位之后应该变化的数值
var sliderbarTop = sliderbar.offsetTop - bannerTop;
// 2. 页面滚动事件 scroll
document.addEventListener('scroll', function() {
// console.log(11);
// window.pageYOffset 页面被卷去的头部
// console.log(window.pageYOffset);
// 3. 当我们页面被卷去的头部大于等于了301 此时 侧边栏就要改为固定定位
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
// 4. 当我们页面滚动到main盒子,就显示goBack模块
if (window.pageYOffset >= mainTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
</script>
</body>
</html>
三大系列总结
三大系列大小对比 | 作用 |
---|---|
element.offsetWidth | 返回自身包括padding、边框、内容区的宽度,返回值不带单位 |
element.clientWidth | 返回自身包括padding、内容区的宽度,不包含边框,返回数值不带单位 |
element.scrollWidth | 返回自身实践的宽度,不含边框,返回数值不带单位 |
他们主要用法:
- offset系列经常用于获得元素位置
offsetLeft offsetTop
- client经常用于获取元素大小
clientWidth clientHeight
- scroll经常用于获取滚动距离
scrollTop scrollLeft
注意页面滚动的距离
通过window.pageXOffset 获得
mouseenter和mouseover的区别
mouseenter 鼠标事件
- 当鼠标移动到元素上时就会触发mouseenter事件
- 类似mouseover,它们两者之间的差别是
- mouseover鼠标经过自身盒子会触发,经过子盒子还会触发。mouseenter 只会经过自身盒子触发
- 之所以这样,就是因为mouseenter不会冒泡
- 跟mouseenter搭配 鼠标离开 mouseleave 同样不会冒泡
<!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>mouseenter和mouseover的区别</title>
<style>
.father {
width: 400px;
height: 400px;
background-color: pink;
margin: 0 auto;
}
.son {
width: 200px;
height: 200px;
background-color: purple;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
var father = document.querySelector('.father');
var son = document.querySelector('.son');
father.addEventListener('mouseenter', function () {
console.log(11);
})
</script>
</body>
</html>
4.动画函数封装
4.1 动画实现原理
核心原理
:通过定时器 setInterval() 不断移动盒子位置。
实现步骤:
- 获得盒子当前位置
- 让盒子在当前位置加上1个移动距离
- 利用定时器不断重复这个操作
- 加一个结束定时器的条件
- 注意此元素需要添加定位,才能使用element.style.left
<!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>动画原理</title>
<style>
div {
position: absolute;
width: 100px;
height: 100px;
background: pink;
transition: all .3;
}
</style>
</head>
<body>
<div></div>
<script>
// 1. 获得盒子当前位置
// 2. 让盒子在当前位置加上1个移动距离
// 3. 利用定时器不断重复这个操作
// 4. 加一个结束定时器的条件
// 5. 注意此元素需要添加定位,才能使用element.style.left
var div = document.querySelector('div');
var timer = setInterval(function() {
if (div.offsetLeft >= 400) {
// 停止动画 本质是停止定时器
clearInterval(timer);
} else {
div.style.left = div.offsetLeft + 4 + 'px';
}
}, 30)
</script>
</body>
</html>
4.2 动画函数简单封装
注意函数需要传递2个参数,动画对象
和移动到的距离
。
<!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>简单动画函数封装</title>
<style>
div {
position: absolute;
width: 100px;
height: 100px;
background: pink;
transition: all .3;
}
span {
position: relative;
top: 200px;
display: block;
width: 150px;
height: 150px;
background-color: purple;
}
</style>
</head>
<body>
<div></div>
<span>夏雨荷</span>
<script>
// 简单动画函数封装 obj目标对象 target目标位置
function animate(obj, target) {
var timer = setInterval(function() {
if (obj.offsetLeft >= target) {
// 停止动画 本质是停止定时器
clearInterval(timer);
} else {
obj.style.left = obj.offsetLeft + 4 + 'px';
}
}, 30)
}
var div = document.querySelector('div');
var span = document.querySelector('span');
animate(div, 500);
animate(span, 300);
</script>
</body>
</html>
4.3 动画函数给不同元素记录不同定时器
如果多个元素都使用这个动画函数,每次都要var声明定时器。我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器)。
核心原理:利用js是一门动态语言,可以很方便的给当前对象添加属性。
<!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>给不同对象添加不同定时器</title>
<style>
div {
position: absolute;
width: 100px;
height: 100px;
background: pink;
transition: all .3;
}
span {
position: relative;
top: 200px;
display: block;
width: 150px;
height: 150px;
background-color: purple;
}
</style>
</head>
<body>
<button>点击之后夏雨荷才走</button>
<div></div>
<span>夏雨荷</span>
<script>
// var obj = {};
// obj.name = 'andy';
// 简单动画函数封装 obj目标对象 target目标位置
// 给不同的元素指定了不同的定时器
function animate(obj, target) {
// 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是让我们元素只有一个定时器执行
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer)
obj.timer = setInterval(function() {
if (obj.offsetLeft >= target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
} else {
obj.style.left = obj.offsetLeft + 4 + 'px';
}
}, 30)
}
var div = document.querySelector('div');
var span = document.querySelector('span');
var btn = document.querySelector('button');
animate(div, 500);
btn.addEventListener('click', function() {
animate(span, 300);
})
</script>
</body>
</html>
4.4 缓动效果原理
缓动动画就是让元素运动速度有所变化,最常见的是让速度慢慢停下来
思路:
- 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
- 核心算法:(目标值-现在的位置)/ 10 作为每次移动的距离 步长
- 停止的条件是:让当前盒子位置等于目标位置就停止定时器
- 注意步长值需要取整
<!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>缓动动画原理</title>
<style>
div {
position: absolute;
width: 100px;
height: 100px;
background: pink;
transition: all .3;
}
span {
position: relative;
top: 200px;
display: block;
width: 150px;
height: 150px;
background-color: purple;
}
</style>
</head>
<body>
<button>点击之后夏雨荷才走</button>
<div></div>
<span>夏雨荷</span>
<script>
// var obj = {};
// obj.name = 'andy';
// 简单动画函数封装 obj目标对象 target目标位置
// 思路:
// 1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
// 2. 核心算法:(目标值 - 现在的位置)/ 10 作为每次移动的距离 步长
// 3. 停止的条件是:让当前盒子位置等于目标位置就停止定时器
function animate(obj, target) {
// 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是让我们元素只有一个定时器执行
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer)
obj.timer = setInterval(function () {
// 步长值写到定时器里面
var step = Math.ceil((target - obj.offsetLeft) / 10)
if (obj.offsetLeft >= target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
} else {
// 把每次加1的这个步长值改为慢慢变小的步长值 步长公式:(目标值 - 现在的位置)/ 10
obj.style.left = obj.offsetLeft + step + 'px';
}
}, 15)
}
var div = document.querySelector('div');
var span = document.querySelector('span');
var btn = document.querySelector('button');
animate(div, 800);
btn.addEventListener('click', function () {
animate(span, 1000);
})
// 匀速动画 就是 盒子当前的位置加上一个固定值 10
// 缓动动画就是盒子当前的位置 + 变化的值(目标值 - 现在的位置)/ 10
</script>
</body>
</html>
4.5 动画函数多个目标值之间移动
可以让动画函数从800移动到500。
当我们点击按钮时候,判断步长是正值还是负值
- 如果是正值,则步长往大了取整。
- 如果是负值,则步长向小了取整
<!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>缓动动画原理</title>
<style>
span {
position: absolute;
top: 200px;
left: 0;
display: block;
width: 150px;
height: 150px;
background-color: purple;
}
</style>
</head>
<body>
<button class="btn500">点击夏雨荷500</button>
<button class="btn800">点击夏雨荷800</button>
<span>夏雨荷</span>
<script>
// var obj = {};
// obj.name = 'andy';
// 简单动画函数封装 obj目标对象 target目标位置
// 思路:
// 1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
// 2. 核心算法:(目标值 - 现在的位置)/ 10 作为每次移动的距离 步长
// 3. 停止的条件是:让当前盒子位置等于目标位置就停止定时器
function animate(obj, target) {
// 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是让我们元素只有一个定时器执行
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer)
obj.timer = setInterval(function () {
// 步长值写到定时器里面
// 把步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
} else {
// 把每次加1的这个步长值改为慢慢变小的步长值 步长公式:(目标值 - 现在的位置)/ 10
obj.style.left = obj.offsetLeft + step + 'px';
}
}, 15)
}
var span = document.querySelector('span');
var btn500 = document.querySelector('.btn500');
var btn800 = document.querySelector('.btn800');
btn500.addEventListener('click', function () {
animate(span, 500);
})
btn800.addEventListener('click', function () {
animate(span, 800);
})
// 匀速动画 就是 盒子当前的位置加上一个固定值 10
// 缓动动画就是盒子当前的位置 + 变化的值(目标值 - 现在的位置)/ 10
</script>
</body>
</html>
4.6 动画函数添加回调函数
回调函数原理
:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调
。
回调函数写的位置:定时器结束的位置。
<!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>i缓动动画添加回调函数</title>
<style>
span {
position: absolute;
top: 200px;
left: 0;
display: block;
width: 150px;
height: 150px;
background-color: purple;
color: #fff;
}
</style>
</head>
<body>
<button class="btn500">点击夏雨荷500</button>
<button class="btn800">点击夏雨荷800</button>
<span>夏雨荷</span>
<script>
// var obj = {};
// obj.name = 'andy';
// 简单动画函数封装 obj目标对象 target目标位置
// 思路:
// 1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
// 2. 核心算法:(目标值 - 现在的位置)/ 10 作为每次移动的距离 步长
// 3. 停止的条件是:让当前盒子位置等于目标位置就停止定时器
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调用的时候 callback()
// 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是让我们元素只有一个定时器执行
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer)
obj.timer = setInterval(function () {
// 步长值写到定时器里面
// 把步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
if (callback) {
// 调用函数
callback();
}
} else {
// 把每次加1的这个步长值改为慢慢变小的步长值 步长公式:(目标值 - 现在的位置)/ 10
obj.style.left = obj.offsetLeft + step + 'px';
}
}, 15)
}
var span = document.querySelector('span');
var btn500 = document.querySelector('.btn500');
var btn800 = document.querySelector('.btn800');
btn500.addEventListener('click', function () {
animate(span, 500, function() {
span.style.backgroundColor = 'red';
});
})
btn800.addEventListener('click', function () {
animate(span, 800, function () {
span.style.background = 'pink';
});
})
// 匀速动画 就是 盒子当前的位置加上一个固定值 10
// 缓动动画就是盒子当前的位置 + 变化的值(目标值 - 现在的位置)/ 10
</script>
</body>
</html>
4.7 动画函数封装到单独js文件里面
因为以后经常使用这个动画函数,可以单独封装到一个js文件里面,使用的时候引用这个js文件即可
<!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>引用animate动画函数</title>
<style>
.sliderbar {
position: absolute;
top: 45%;
right: 0;
}
.sliderbar span {
position: relative;
display: block;
width: 40px;
height: 40px;
line-height: 40px;
background: purple;
text-align: center;
color: #fff;
z-index: 1;
transition: all .4;
}
.con {
position: absolute;
top: 0;
/* left: 0; */
width: 200px;
height: 40px;
line-height: 40px;
text-align: center;
background: purple;
color: #fff;
}
</style>
<script src="03-animate.js"></script>
</head>
<body>
<div class="sliderbar">
<span>←</span>
<div class="con">问题反馈</div>
</div>
<script>
// 当我们鼠标经过 sliderbar 就会让 con这个盒子滑动到左侧
// 当我们鼠标离开 sliderbar 就会让 con这个盒子滑动到右侧
var sliderbar = document.querySelector('.sliderbar');
var con = document.querySelector('.con');
sliderbar.addEventListener('mouseenter', function() {
// animate(obj, target, callback)
animate(con, -160, function() {
// 当我们动画执行完毕就把 ← 改为 →
sliderbar.children[0].innerHTML = '→'
});
})
sliderbar.addEventListener('mouseleave', function() {
animate(con, 0, function() {
sliderbar.children[0].innerHTML = '←'
});
})
</script>
</body>
</html>
5.常见网页特效案例
案例:网页轮播图
轮播图也称为焦点图,是网页中比较常见的网页特效。
功能需求:
-
鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
- 因为js较多,我们单独新建js文件夹,再新建js文件,引入页面中。
- 此时需要添加load事件
鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
- 显示隐藏display按钮。
window.addEventListener('load', function () { // 1. 获取元素 var arrow_l = document.querySelector('.arrow-l'); var arrow_r = document.querySelector('.arrow-r'); var focus = document.querySelector('.focus'); // 2. 鼠标经过focus模块,就显示隐藏左右箭头 focus.addEventListener('mouseenter', function () { arrow_l.style.display = 'block'; arrow_r.style.display = 'block'; }) focus.addEventListener('mouseleave', function () { arrow_l.style.display = 'none'; arrow_r.style.display = 'none'; }) })
-
点击右侧按钮一次,图片往左播放一张,以此类推,左侧按钮同理。
-
图片播放的同时,下面小圆圈模块跟谁一起变化。
-
点击小圆圈,可以播放相应图片。
动态生成小圆圈
- 核心思路:小圆圈的个数要跟图片张数一致
- 所以首先先得到ul里面图片的张数(图片放入li里面,所以就是li的个数)
- 利用循环动态生成小圆圈(这个小圆圈要放入ol里面)
- 创建节点createElement(‘li’)
- 插入节点ol.appendChild(li)
- 第一个小圆圈需要添加current类
// 3.动态生成小圆圈 有几张图片,我就生成几个小圆圈 var ul = focus.querySelector('ul'); var ol = focus.querySelector('.circle'); // ul 移动的距离 小圆圈的索引号 乘以图片的宽度 注意是负值 var focusWidth = focus.offsetWidth; // console.log(ul.children.length); for (var i = 0; i < ul.children.length; i++) { // 创建一个li var li = document.createElement('li'); // 将创建的li插入到ol里 // 记录当前小圆圈的索引号 通过自定义属性 li.setAttribute('index-data', i) ol.appendChild(li); } // 把ol里面的第一个小li设置类名为 current ol.children[0].className = 'current';
-
鼠标不经过轮播图,轮播图也会自动播放图片。
-
鼠标经过,轮播图模块,自动播放停止。
- 小圆圈的排他思想
- 点击当前小圆圈,就添加current类
- 其余的小圆圈就移除这个current类
- 注意:我们在刚才生成小圆圈的同时,就可以直接绑定这个点击事件了
// 4. 小圆圈的排他思想 我们可以在生成的同时直接绑定事件
li.addEventListener('mouseover', function () {
// 干掉所有人 把所有的小li清除 current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下我自己 当前的小li添加current类名
this.className = 'current';
})
点击小圆圈滚动图片
- 此时用到animate动画函数,将js文件引入(注意,因为index.js依赖animate.js所以,animate.js要写到index.js上面)
- 使用动画函数的前提,该元素必须要有定位
- 注意是ul移动而不是li
- 滚动图片的核心算法:点击某个小圆圈,就让图片滚动小圆圈的
索引号乘以图片的宽度
作为ul移动的距离 - 此时需要知道小圆圈的索引号,我们可以在生成小圆圈的时候,给他设置一个自定义属性,点击的时候获取这个自定义属性即可。
// 5. 点击小圆圈移动图片 当然移动的是ul
// 当我们点击了某个小li 就拿到当前小li的索引号
var index_data = this.getAttribute('index-data');
// 当我们点击了某个小li 就要把这个小li的索引号给 num
num = index_data;
// 当我们点击了某个小li 就要把这个小li的索引号给 circle
circle = index_data;
// console.log(index_data);
// console.log(focusWidth);
animate(ul, -index_data * focusWidth)
点击右侧按钮一次,就让图片滚动一张。
- 声明一个变量num,点击一次,自增1,让这个变量乘以图片宽度,就是ul的滚动距离
- 图片无缝滚动原理
- 把ul第一个li复制一份,放到ul的最后面
- 当图片滚动到克隆的最后一张图片时,让ul快速的,不做动画的跳到最左侧:left为0
- 同时num赋值为0,可以从新开始滚动图片了
// 7. 点击右侧按钮 图片滚动一张
var num = 0;
// circle 控制小圆圈的播放
var circle = 0;
arrow_r.addEventListener('click', function () {
// 如果走到了最后一张图片 此时我们的ul要快速复原 left改为0
// console.log(num);
if (num == ol.children.length) {
ul.style.left = '0px';
num = 0;
}
num++;
animate(ul, -num * focusWidth)
克隆第一张图片
- 克隆ul第一个li cloneNode() 加 true 深克隆 复制里面的子节点 false 浅克隆
// 6. 克隆第一张图片(li),放到ul最后面
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
- 点击右侧按钮,小圆圈跟随变化
- 最简单的做法是再声明一个变量circle,每次点击自增1,注意左侧按钮也需要这个变量 ,因此要声明全局变量
- 但是图片有5张,我们小圆圈只有4个少一个,必须加一个判断条件
- 如果circle == 4 就从新复原为0
// 8. 点击右侧按钮,小圆圈跟随变化 可以再声明一个变量控制小圆圈的播放
circle++;
// 如果我们circle == 4 说明走到最后我们克隆的这张图片了,我们就复原
if (circle == ul.children.length - 1) {
circle = 0;
}
// 先清除其他小圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前小圆圈的current类名
ol.children[circle].className = 'current';
- 添加一个定时器
- 自动播放轮播图,实际就类似点击了右侧按钮
- 此时我们使用
手动调用
右侧按钮点击事件
arrow_r.click() - 鼠标经过focus就停止定时器
- 鼠标离开就开启定时器
5.1 节流阀
防止轮播图按钮连续点击造成播放过快。
节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。
核心思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。
开始设置一个变量 var flag = true;
if (flag) {flag = false;do something} 关闭水龙头
利用回调函数动画执行完毕,flag = true 打开水龙头
<div class="main">
<div class="focus fl">
<!-- 左侧箭头 -->
<a href="javascript: void(0);" class="arrow-l"><</a>
<!-- 右侧箭头 -->
<a href="javascript: void(0);" class="arrow-r">></a>
<!-- 核心的滚动区域 -->
<ul>
<li>
<a href="#"><img src="upload/focus.png" alt=""></a>
</li>
<li>
<a href="#"><img src="upload/focus1.jpg" alt=""></a>
</li>
<li>
<a href="#"><img src="upload/focus2.jpg" alt=""></a>
</li>
<li>
<a href="#"><img src="upload/focus3.jpg" alt=""></a>
</li>
</ul>
<!-- 小圆点 -->
<ol class="circle">
</ol>
</div>
.focus {
overflow: hidden;
position: relative;
float: left;
width: 721px;
height: 455px;
}
.focus ul {
position: absolute;
top: 0;
left: 0;
width: 500%;
}
.focus ul li {
float: left;
}
.arrow-l,
.arrow-r {
display: none;
position: absolute;
top: 50%;
width: 36px;
height: 50px;
line-height: 48px;
text-align: center;
font-size: 26px;
background: rgba(0,0,0, .2);
color: #fff;
transform: translate(0, -50%);
z-index: 2;
}
.focus .arrow-r {
right: 0;
}
.circle {
position: absolute;
bottom: 10px;
left: 50px;
}
.current {
background-color: #fff;
}
.circle li {
float: left;
width: 15px;
height: 15px;
margin: 0 6px;
border: 2px solid #ccc;
border-radius: 50%;
cursor: pointer;
}
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调用的时候 callback()
// 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是让我们元素只有一个定时器执行
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer)
obj.timer = setInterval(function () {
// 步长值写到定时器里面
// 把步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
// if (callback) {
// // 调用函数
// callback();
// }
callback && callback();
} else {
// 把每次加1的这个步长值改为慢慢变小的步长值 步长公式:(目标值 - 现在的位置)/ 10
obj.style.left = obj.offsetLeft + step + 'px';
}
}, 15)
}
window.addEventListener('load', function () {
// 1. 获取元素
var arrow_l = document.querySelector('.arrow-l');
var arrow_r = document.querySelector('.arrow-r');
var focus = document.querySelector('.focus');
// 2. 鼠标经过focus模块,就显示隐藏左右箭头
focus.addEventListener('mouseenter', function () {
arrow_l.style.display = 'block';
arrow_r.style.display = 'block';
clearInterval(timer);
timer = null; // 清除定时器变量
})
focus.addEventListener('mouseleave', function () {
arrow_l.style.display = 'none';
arrow_r.style.display = 'none';
timer = setInterval(function () {
arrow_r.click();
}, 2000)
})
// 3.动态生成小圆圈 有几张图片,我就生成几个小圆圈
var ul = focus.querySelector('ul');
var ol = focus.querySelector('.circle');
// ul 移动的距离 小圆圈的索引号 乘以图片的宽度 注意是负值
var focusWidth = focus.offsetWidth;
// console.log(ul.children.length);
for (var i = 0; i < ul.children.length; i++) {
// 创建一个li
var li = document.createElement('li');
// 将创建的li插入到ol里
// 记录当前小圆圈的索引号 通过自定义属性
li.setAttribute('index-data', i)
ol.appendChild(li);
// 4. 小圆圈的排他思想 我们可以在生成的同时直接绑定事件
li.addEventListener('click', function () {
// 干掉所有人 把所有的小li清除 current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下我自己 当前的小li添加current类名
this.className = 'current';
// 5. 点击小圆圈移动图片 当然移动的是ul
// 当我们点击了某个小li 就拿到当前小li的索引号
var index_data = this.getAttribute('index-data');
// 当我们点击了某个小li 就要把这个小li的索引号给 num
num = index_data;
// 当我们点击了某个小li 就要把这个小li的索引号给 circle
circle = index_data;
// console.log(index_data);
// console.log(focusWidth);
animate(ul, -index_data * focusWidth)
})
}
// 把ol里面的第一个小li设置类名为 current
ol.children[0].className = 'current';
// 6. 克隆第一张图片(li),放到ul最后面
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
// 7. 点击右侧按钮 图片滚动一张
var num = 0;
// circle 控制小圆圈的播放
var circle = 0;
// flag 节流阀
var flag = true;
arrow_r.addEventListener('click', function () {
if (flag) {
flag = false;
// 如果走到了最后一张图片 此时我们的ul要快速复原 left改为0
// console.log(num);
if (num == ol.children.length) {
ul.style.left = '0px';
num = 0;
}
num++;
animate(ul, -num * focusWidth, function () {
flag = true; // 打开节流阀
})
// 8. 点击右侧按钮,小圆圈跟随变化 可以再声明一个变量控制小圆圈的播放
circle++;
// 如果我们circle == 4 说明走到最后我们克隆的这张图片了,我们就复原
// if (circle == ul.children.length - 1) {
// circle = 0;
// }
circle == ul.children.length - 1 ? circle = 0 : circle;
circleChange()
}
})
// 9. 左侧按钮
arrow_l.addEventListener('click', function () {
if (flag) {
flag = false;
// 如果走到了最后一张图片 此时我们的ul要快速复原 left改为0
// console.log(num);
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -num * focusWidth + 'px';
}
num--;
animate(ul, -num * focusWidth, function() {
flag = true;
})
// 8. 点击右侧按钮,小圆圈跟随变化 可以再声明一个变量控制小圆圈的播放
circle--;
// 如果我们circle < 0 说明是第一张图片 则小圆圈要改为第四个小圆圈(3)
// if (circle < 0) {
// circle = ol.children.length - 1;
// }
circle = circle < 0 ? ol.children.length - 1 : circle;
circleChange()
}
})
function circleChange() {
// 先清除其他小圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前小圆圈的current类名
ol.children[circle].className = 'current';
}
// 10. 自动播放轮播图
var timer = setInterval(function () {
arrow_r.click();
}, 2000)
})
案例:返回顶部
滚动窗口至文档中的特定位置。
window.scroll(x, y)
注意里面的x和y不跟单位
- 带有动画的返回顶部
- 此时我们可以继续使用我们封装的动画函数
- 只需要把所有的left相关的值改为跟页面垂直滚动距离相关就可以了
- 页面滚动了多少,可以通过window.pageYOffset得到
- 最后是页面滚动,使用window.scroll(x, y)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>返回顶部案例</title>
<style>
.slider-bar {
position: absolute;
left: 50%;
top: 300px;
margin-left: 600px;
width: 45px;
height: 130px;
background-color: pink;
}
.w {
width: 1200px;
margin: 10px auto;
}
.header {
height: 150px;
background-color: purple;
}
.banner {
height: 250px;
background-color: skyblue;
}
.main {
height: 1000px;
background-color: yellowgreen;
}
span {
display: none;
position: absolute;
bottom: 0;
}
.goBack {
cursor: pointer;
}
</style>
</head>
<body>
<div class="slider-bar">
<span class="goBack">返回顶部</span>
</div>
<div class="header w">头部区域</div>
<div class="banner w">banner区域</div>
<div class="main w">主体部分</div>
<script>
// 1. 获取元素
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
// 获取 main 主体元素
var main = document.querySelector('.main');
var goBack = document.querySelector('.goBack');
var mainTop = main.offsetTop;
// banner.offsetTop 就是被卷去的头部的大小 一定要写到滚动事件的外面
var bannerTop = banner.offsetTop;
// 当我们侧边栏固定定位之后应该变化的数值
var sliderbarTop = sliderbar.offsetTop - bannerTop;
// 2. 页面滚动事件 scroll
document.addEventListener('scroll', function () {
// console.log(11);
// window.pageYOffset 页面被卷去的头部
// console.log(window.pageYOffset);
// 3. 当我们页面被卷去的头部大于等于了301 此时 侧边栏就要改为固定定位
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
// 4. 当我们页面滚动到main盒子,就显示goBack模块
if (window.pageYOffset >= mainTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
// 3. 当我们点击了返回顶部模块,就让窗口滚动到页面的最上方
goBack.addEventListener('click', function () {
// 里面的 x 和 y 不跟单位 直接写数字即可
// window.scroll(0, 0);
animate(window, 0);
});
// 动画函数
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调用的时候 callback()
// 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是让我们元素只有一个定时器执行
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer)
obj.timer = setInterval(function () {
// 步长值写到定时器里面
// 把步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - window.pageYOffset) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (window.pageYOffset == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
callback && callback();
} else {
// 把每次加1的这个步长值改为慢慢变小的步长值 步长公式:(目标值 - 现在的位置)/ 10
// obj.style.left = window.pageYOffset + step + 'px';
window.scroll(0, window.pageYOffset + step)
}
}, 15)
}
</script>
</body>
</html>
案例:筋斗云案例
- 鼠标经过某个小li,筋斗云跟着到当前小li位置
- 鼠标离开这个小li,筋斗云复原为原来的位置
- 鼠标点击了某个小li,筋斗云就会留在点击这个小li的位置
案例分析
- 利用动画函数做动画效果
- 原先筋斗云的起始位置是0
- 鼠标经过某个小li,把当前的offsetLeft位置作为目标值即可
- 鼠标离开某个小li,就把目标值设为0
- 如果点击了某个小li,就把当前的位置存储起来,当鼠标离开,筋斗云就回到这个位置
<!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>筋斗云案例</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
background: #000;
}
a {
color: #000;
text-decoration: none;
text-align: center;
}
li {
list-style: none;
}
.c-nav {
position: relative;
margin: 100px auto;
width: 1000px;
height: 42px;
background: #fff url(images/rss.png) no-repeat right center;
border-radius: 6px;
}
.c-nav ul {
position: absolute;
}
.c-nav ul li {
float: left;
margin: 10px 11px;
}
.cloud {
position: absolute;
top: 0;
left: 0;
display: block;
width: 83px;
height: 42px;
background: url(images/cloud.gif) no-repeat;
}
.c-nav li a:hover {
color: white;
}
.c-nav li.current a {
color: #0dff1d;
}
</style>
<script src="03-animate.js"></script>
<script>
window.addEventListener('load', function() {
// 1. 获取元素
var cloud = document.querySelector('.cloud');
var c_nav = document.querySelector('.c-nav');
var lis = c_nav.querySelectorAll('li');
// 2. 给所有的小li绑定事件
var current = 0;
// 这个current作为筋斗云的起始位置
for (var i = 0; i < lis.length; i++) {
// (1) 鼠标经过把当前的小li的位置作为目标值
lis[i].addEventListener('mouseenter', function() {
animate(cloud, this.offsetLeft - 10);
})
// (2) 鼠标离开就回到起始的位置
lis[i].addEventListener('mouseleave', function() {
animate(cloud, current);
})
lis[i].addEventListener('click', function() {
current = this.offsetLeft - 10;
})
}
})
</script>
</head>
<body>
<div class="c-nav" id="c_nav">
<span class="cloud"></span>
<ul>
<li class="current"><a href="#">首页新闻</a></li>
<li><a href="#">师资力量</a></li>
<li><a href="#">活动策划</a></li>
<li><a href="#">企业文化</a></li>
<li><a href="#">招聘信息</a></li>
<li><a href="#">公司简介</a></li>
<li><a href="#">你是佩奇</a></li>
<li><a href="#">啥是佩奇</a></li>
</ul>
</div>
</body>
</html>
移动端网页特效
1. 触屏事件
1.1 触屏事件概述
移动端浏览器兼容性好,我们不需要考虑以前js的兼容性问题,可以放心的使用原生js书写效果,但是移动端也有自己独特的地方。比如触屏事件touch
(也称触摸事件),Android和iOS都有。
常见的触屏事件如下:
触屏touch事件 | 说明 |
---|---|
touchstart | 手指触摸到一个dom元素时触发 |
touchmove | 手指在一个dom元素上滑动时触发 |
touchend | 手指从一个dom元素上移开时触发 |
<!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>
div {
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
</body>
</html>
<script>
var div = document.querySelector('div');
// 1. 手指触摸DOM元素事件
div.addEventListener('touchstart', function() {
console.log('我摸了你');
})
// 2. 手指在DOM元素身上移动事件
div.addEventListener('touchmove', function() {
console.log('我继续摸');
})
// 3. 手指离开DOM元素事件
div.addEventListener('touchend', function() {
console.log('轻轻的我走了');
})
</script>
1.2 触摸事件对象(TouchEvent)
TouchEvent是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触电,使开发者可以检测触点的移动,触点的增加和减少,等等
touchstart、touchmove、touchend三个事件都会各自有事件对象。
触摸事件对象重点我们看三个常见对象列表:
触摸列表 | 说明 |
---|---|
touches | 正在触摸屏幕的所有手指的一个列表 |
targetTouches | 正在触摸当前DOM元素上的手指的一个列表 |
changeTouches | 手指状态发生了改变的列表,从无到有,从有道无的变化 |
<!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;
}
div {
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
</body>
</html>
<script>
var div = document.querySelector('div');
// 1. 手指触摸DOM元素事件
div.addEventListener('touchstart', function(e) {
// console.log(e);
// touches 正在触摸屏幕的所有手指的列表
// targetTouches 正在触摸当前DOM元素的手指列表
// changeTouches 手指状态发生了改变的列表,从无到有或者从有到无
// 因为我们一般都是触摸元素,所以最经常使用的是 targetTouches
console.log(e.targetTouches[0]);
// targetTouches[0] 就可以得到正在触摸dom元素的第一个手指的相关信息比如手指的坐标等等
})
// 2. 手指在DOM元素身上移动事件
div.addEventListener('touchmove', function() {
})
// 3. 手指离开DOM元素事件
div.addEventListener('touchend', function() {
console.log('轻轻的我走了');
// 当我们手指离开屏幕的时候,就没有了touches和targetTouches列表,但是会有changeTouches
})
</script>
1.3 移动端拖动元素
- touchstart、touchmove、touchend可以实现拖动元素
- 但是拖动元素需要当前手指的坐标值我们可以使用targetTouches[0]里面的pageX和pageY
- 移动端拖动的原理:手指移动中,计算出手指移动的距离。然后用盒子原来的位置+手指移动的距离
- 手指移动的距离:手指滑动中的位置减去 手指刚开始触摸的位置
拖动元素三部曲:
- 触摸元素touchstart:获取手指初始坐标,同时获得盒子原来的位置
- 移动手指touchmove:计算手指滑动的距离,并且移动盒子
- 离开手指touchend;
注意:手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动e.preventDefault();
<!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>
div {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
</body>
</html>
<script>
// 触摸元素touchstart:获取手指初始坐标,同时获得盒子原来的位置
// 移动手指touchmove:计算手指滑动的距离,并且移动盒子
// 离开手指touchend;
var div = document.querySelector('div');
var startX = 0;
var startY = 0; // 手指初始坐标
var x = 0;
var y = 0; // 盒子原来的位置
div.addEventListener('touchstart', function (e) {
// 获取手指初始坐标
startX = e.targetTouches[0].pageX;
startY = e.targetTouches[0].pageY;
x = this.offsetLeft;
y = this.offsetTop;
})
div.addEventListener('touchmove', function (e) {
// 阻止屏幕滚动的默认行为
e.preventDefault();
// 计算手指的移动距离: 手指移动之后的坐标减去手指初始的坐标
var moveX = e.targetTouches[0].pageX - startX;
var moveY = e.targetTouches[0].pageY - startY;
// 移动盒子 盒子原来的位置 + 手指移动的距离
this.style.left = x + moveX + 'px';
this.style.top = y + moveY + 'px';
})
</script>
案例:移动端轮播图
移动端轮播图功能和pc端基本一致。
- 可以自动播放图片
- 手指可以拖动播放轮播图
案例分析
自动播放功能
-
开启定时器
-
移动端移动,可以使用translate移动
自动播放功能-无缝滚动
- 注意,我们判断条件是要等到图片滚动完毕再去判断,就是过渡完成后判断
- 此时需要添加检测过渡完成事件 transitionend
- 判断条件:如果索引号等于3说明走到最后一张图片,此时索引号要复原为0
- 此时图片,去掉过渡效果,然后移动
classList属性
classList属性是HTML5新增的一个属性,返回元素的类名。但是ie10以上版本支持。
该元素用于在元素中添加,移除以及切换css类。有以下方法
添加类:
element.classList.add(‘类名’);
focus.classList.add('current');
移除类:
element.classList.remove(‘类名’);
focus.classList.remove('current');
切换类:
element.classList.toggle(‘类名’);
focus.classList.toggle('current');
小圆点跟随变化效果
- 把ol里面li带有current类名的选出来去掉类名
remove
- 让当前索引号的小li加上current
add
但是,是等着过渡结束之后变化,所以这个写到transitionend事件里面
手指滑动轮播图
本质就是ul跟谁手指移动,简单来说就是移动端拖动元素
- 触摸元素touchstart: 获取手指初始坐标
- 移动手指touchmove:计算手指的滑动距离,并且移动盒子
- 手指离开touchend:根据滑动的距离分不同的情况
- 如果移动距离小于某个像素就回弹原来位置
- 如果移动距离大于某个像素就上一张下一张滑动。
window.addEventListener('load', function () {
// 1. 获取元素
var focus = document.querySelector('.focus');
var ul = focus.children[0];
var ol = focus.children[1];
// 获取focus的宽度
var w = focus.offsetWidth;
// 2. 利用定时器自定轮播图片
var index = 0;
var flag = false;
var timer = setInterval(function () {
index++;
var translateX = -index * w;
ul.style.transition = 'all .4s';
ul.style.transform = 'translate(' + translateX + 'px)';
}, 2000)
// 等着我们过渡完之后,再去判断监听过渡完成的事件 transitionend
ul.addEventListener('transitionend', function () {
// 无缝滚动
if (index >= ul.children.length - 2) {
index = 0;
// 去掉过渡效果这样让我们的ul快速的跳到目标位置
ul.style.transition = 'none';
// 利用最新索引号乘以宽度,去滚动图片
var translateX = -index * w;
ul.style.transform = 'translate(' + translateX + 'px)';
} else if (index < 0) {
index = ul.children.length - 3;
ul.style.transition = 'none';
// 利用最新索引号乘以宽度,去滚动图片
var translateX = -index * w;
ul.style.transform = 'translate(' + translateX + 'px)';
}
// (3) 小圆点跟随变化
// 1. 把ol里面li带有current类名的选出来去掉类名 remove
ol.querySelector('li.current').classList.remove('current');
// 2. 让当前索引号的小li加上current add
ol.children[index].classList.add('current');
})
// (4) 手指滑动轮播图
// 1. 触摸元素touchstart: 获取手指初始坐标
var startX = 0;
var moveX = 0;
ul.addEventListener('touchstart', function (e) {
startX = e.targetTouches[0].pageX;
// 手指触摸的时候停止定时器
clearInterval(timer);
})
// 2. 移动手指touchmove:计算手指的滑动距离,并且移动盒子
ul.addEventListener('touchmove', function (e) {
e.preventDefault(); // 阻止屏幕滚动的行为
// 计算移动距离
moveX = e.targetTouches[0].pageX - startX;
if (index >= ul.children.length - 2) {
index = 0;
// 去掉过渡效果这样让我们的ul快速的跳到目标位置
ul.style.transition = 'none';
// 利用最新索引号乘以宽度,去滚动图片
var translateX = -index * w;
ul.style.transform = 'translate(' + translateX + 'px)';
} else if (index < 0) {
index = ul.children.length - 3;
ul.style.transition = 'none';
// 利用最新索引号乘以宽度,去滚动图片
var translateX = -index * w;
ul.style.transform = 'translate(' + translateX + 'px)';
}
// 移动盒子: 盒子原来的位置 + 手指移动的距离
var translateX = -index * w + moveX;
// 手指拖动的时候,不需要动画效果所以要取消过渡效果
ul.style.transition = 'none';
ul.style.transform = 'translate(' + translateX + 'px)';
flag = true; // 如果用户手指移动过,再去判断否则不做判断效果
})
// 手指离开touchend:根据滑动的距离分不同的情况
ul.addEventListener('touchend', function () {
// (1) 如果移动距离大于50像素我们就播放上一张或者下一张
if (flag) {
if (Math.abs(moveX) > 50) {
// 如果是右划就是播放上一张moveX是正值
// if (moveX > 0) {
// index--;
// } else {
// // 如果是左划就是播放上一张moveX是负值
// index++;
// }
moveX > 0 ? index-- : index++;
var translateX = -index * w;
ul.style.transition = 'all .4s';
ul.style.transform = 'translate(' + translateX + 'px)';
} else {
// (2) 如果移动距离小于50像素我们就回弹
var translateX = -index * w;
ul.style.transition = 'all .1s';
ul.style.transform = 'translate(' + translateX + 'px)';
}
flag = false;
}
// 手指离开重启定时器
clearInterval(timer);
timer = setInterval(function () {
index++;
var translateX = -index * w;
ul.style.transition = 'all .4s';
ul.style.transform = 'translate(' + translateX + 'px)';
}, 2000)
})
})
案例:返回顶部
当页面滚动到某个地方,就显示,否则隐藏
点击可以返回顶部
案例分析
- 滚动某个地方显示
- 事件:scroll页面滚动事件
- 如果被卷去的头部(window.scrollY)大于某个数值
- 点击,window.scroll(0, 0) 返回顶部
2. 移动端常见特效
2.1 click延时解决方案
移动端click事件会有300ms的延时,原因是移动端屏幕双击会缩放(double tap to zoom)页面。
解决方案:
- 禁用缩放。浏览器禁用默认的双击缩放行为并且去掉300ms的点击延迟。
<meta name="viewport" content="user-scalable=no">
- 利用touch事件自己封装这个事件解决300ms延迟。
原理就是:
- 当我们手指触摸屏幕,记录当前触摸时间
- 当我们手指离开屏幕,用离开的时间减去触摸的时间
- 如果时间小于150ms,并且没有滑动屏幕,那么我们就定位为点击
// 封装tap,解决click 300ms 延时
function tap(obj, callback) {
var isMove = false;
var startTime = 0; // 记录触摸时候的时间变量
obj.addEventListener('touchstart', function(e) {
startTime = Date.now(); // 记录触摸时间
});
obj.addEventListener('touchmove', function(e) {
isMove = true; // 看看是否有滑动,有滑动算拖拽,不算点击
})
obj.addEventListener('touchend', function(e) {
if (!isMove && (Date.now() - startTime < 150)) { // 如果手指触摸和离开时间小于150ms算点击
callback && callback(); // 执行回调函数
}
isMove = false; // 取反 重置
startTime = 0;
})
}
tap(div, function() {
// 执行代码
})
- 使用插件。fastclick插件解决300ms延迟。
3. 移动端常用开发插件
3.1 什么是插件
移动端要求的是快速开发,所以我们经常会借助于一些插件来帮我们完成操作,那么什么是插件呢?
js插件就是js文件,
它遵循一定规范编写,方便程序展示效果,拥有特定功能且方便调用。如轮播图和瀑布流插件。
特点:它一般是为了解决某个问题而专门存在,其功能单一,并且比较小。
我们以前写的animate.js也算一个最简单的插件
fastclick插件解决300ms延迟。使用延时
GitHub官网地址:https://github.com/ftlabs/fastclick
3.2 插件的使用
- 引入js文件
- 按照相关插件规定语法使用
3.3 Swiper插件的使用
中文官网地址:https://www.swiper.com.cn/
- 引入插件相关文件
- 按照规定语法使用
3.4 其他移动端常见插件
- superslide:http://www.superslide2.com/
- iscroll:https://github.com/cubiq/iscroll
3.5 插件的使用总结
- 确认插件实现的功能
- 去官网查看使用说明
- 下载插件
- 打开demo实例文件,查看需要引入的相关文件,并且引入
- 复制demo实例文件中的结构html,样式css以及js代码
3.6 练习-移动端视频插件 zy.mdeia.js
H5给我们提供了video标签,但是浏览器的支持情况不同。
不同的.视频格式文件,我们可以通过source解决。
但是外观样式,还有暂停,播放,全屏等功能我们只能自己写代码解决。
这个时候我们可以使用插件方式来制作。
4. 移动端常用开发框架
4.1 框架概述
框架,顾名思义就是一套架构,它会基于自身的特点向用户提供一套
较为完整的解决方案。框架的控制权在框架本身,使用者要按照框架所规定的某种规范进行开发。
插件一般是为了解决某个问题而专门存在,其功能单一,并且比较小。
前端常用的框架有Bootstart、Vue、Angular、React
等。既能开发pc端,也能开发移动端
前端常用的移动端插件有swiper、superslide、iscroll
等。
框架:大而全,一整套解决方案
插件:小而专一,某个功能的解决方案
本地存储
1. 本地存储
随着互联网的快速发展,基于网页的应用越来越普遍,同时也变的越来越复杂,为了满足各种各样的需求,会经常性在本地存储大量的数据,HTML5规范提出了相关解决方案
本地存储特性
- 数据存储在用户浏览器中
- 设置、读取方便、甚至页面刷新不丢失数据
- 容量较大,sessionStorage约5M、localStorage约20M
- 只能存储字符串,可以将对象JSON.stringify()编码后存储
fastclick插件解决300ms延迟。使用延时
GitHub官网地址:
2.window.sessionStorage
- 生命周期为关闭浏览器窗口
- 在同一个窗口(页面)下数据可以共享
- 以键值对的形式存储使用
存储数据:
sessionStorage.setItem(key, value)
获取数据:
sessionStorage.getItem(key)
删除数据:
sessionStorage.removeItem(key)
删除所有数据:
sessionStorage.clear()
<!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>本地存储之sessionStorage</title>
</head>
<body>
<input type="text" autofocus="autofocus">
<button class="set">存储数据</button>
<button class="get">获取数据</button>
<button class="remove">删除数据</button>
<button class="del">清空所有数据</button>
</body>
</html>
<script>
var ipt = document.querySelector('input');
var set = document.querySelector('.set')
var get = document.querySelector('.get')
var remove = document.querySelector('.remove')
var del = document.querySelector('.del')
var flag = 0;
var num = 0;
set.addEventListener('click', function () {
// 当我们点击了之后, 就可以把表单里面的数据存储起来
// console.log(11);
var val = ipt.value
if (ipt.value == '') {
alert('请输入内容啦,小伙计')
} else {
if (flag === 0) {
sessionStorage.setItem('username', val)
flag = 1;
ipt.value = ''
} else if (flag === 1) {
sessionStorage.setItem('pwd', val)
flag = 2;
ipt.value = ''
} else if (sessionStorage.getItem('username') == null && sessionStorage.getItem('pwd') == null) {
flag = 0
} else {
alert('超出存储数量了啦!')
}
}
})
get.addEventListener('click', function () {
console.log(sessionStorage.getItem('username'));
})
remove.addEventListener('click', function () {
if (sessionStorage.getItem('username') == null && sessionStorage.getItem('pwd') == null) {
alert('没有数据可删了啦, 不要再点啦!')
} else if (num === 0) {
sessionStorage.removeItem('username')
num = 1;
ipt.value = ''
} else {
sessionStorage.removeItem('pwd')
num = 0;
ipt.value = ''
}
})
del.addEventListener('click', function () {
if (sessionStorage.getItem('username') == null && sessionStorage.getItem('pwd') == null) {
alert('没有数据可删了啦, 不要再点啦!')
} else {
sessionStorage.clear()
ipt.value = ''
}
})
document.addEventListener('keyup', function (e) {
if (e.keyCode === 13) {
set.click()
}
})
</script>
3.window.localStorage
- 生命周期永久生效,除非手动删除否则关闭页面也会存在
- 可以多窗口(页面)共享(同一个浏览器可以共享)
- 以键值对的形式存储使用
存储数据:
localStorage.setItem(key, value)
获取数据:
localStorage.getItem(key)
删除数据:
localStorage.removeItem(key)
删除所有数据:
localStorage.clear()
<!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>本地存储之localStorage</title>
</head>
<body>
<input type="text" autofocus="autofocus">
<button class="set">存储数据</button>
<button class="get">获取数据</button>
<button class="remove">删除数据</button>
<button class="del">清空所有数据</button>
</body>
</html>
<script>
var ipt = document.querySelector('input');
var set = document.querySelector('.set')
var get = document.querySelector('.get')
var remove = document.querySelector('.remove')
var del = document.querySelector('.del')
var flag = 0;
var num = 0;
set.addEventListener('click', function () {
// 当我们点击了之后, 就可以把表单里面的数据存储起来
// console.log(11);
var val = ipt.value
if (ipt.value == '') {
alert('请输入内容啦,小伙计')
} else {
if (flag === 0) {
localStorage.setItem('username', val)
flag = 1;
ipt.value = ''
} else if (flag === 1) {
localStorage.setItem('pwd', val)
flag = 2;
ipt.value = ''
} else if (localStorage.getItem('username') == null && localStorage.getItem('pwd') == null) {
flag = 0;
} else {
alert('超出存储数量了啦!')
}
}
})
get.addEventListener('click', function () {
console.log(localStorage.getItem('username'));
})
remove.addEventListener('click', function () {
if (localStorage.getItem('username') == null && localStorage.getItem('pwd') == null) {
alert('没有数据可删了啦, 不要再点啦!')
} else if (num === 0) {
localStorage.removeItem('username')
num = 1;
ipt.value = ''
} else {
localStorage.removeItem('pwd')
num = 0;
ipt.value = ''
}
})
del.addEventListener('click', function () {
if (localStorage.getItem('username') == null && localStorage.getItem('pwd') == null) {
alert('没有数据可删了啦, 不要再点啦!')
} else {
localStorage.clear()
ipt.value = ''
}
})
document.addEventListener('keyup', function (e) {
if (e.keyCode === 13) {
set.click()
}
})
</script>
案例:记住用户名
如果勾选记住用户名,下次用户打开浏览器,就在文本框里面自动显示上次登录的用户名
案例分析
- 把数据存起来,用到本地存储
- 关闭页面,也可以显示用户名,所以用到localStorage
- 打开页面,先判断是否有这个用户名,如果有,就在表单里面显示用户名,并且勾选复选框
- 当复选框发生改变的时候change事件
- 如果勾选,就存储,否则就移除
<!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>记住用户名</title>
</head>
<body>
<input type="text" id="username">
<label for="remember"><input type="checkbox" name="checkbox" id="remember">记住用户名</label>
</body>
</html>
<script>
var username = document.querySelector('#username')
var remember = document.querySelector('#remember')
if (localStorage.getItem('username')) {
username.value = localStorage.getItem('username')
remember.checked = true;
}
remember.addEventListener('change', function() {
if (this.checked) {
localStorage.setItem('username', username.value)
} else {
localStorage.removeItem('username')
}
})
</script>
案例:toDoList
- 刷新页面不会丢失数据,因此需要用到本地存储localStorage
- 核心思路:不管按下回车,还是点击复选框,都是把本地存储的数据加载到页面中,这样保证刷新页面不会丢失数据
- 存储的数据格式:var todolist = [{title: ‘xxx’, done: false}]
- 注意点1:本地存储localStorage里面只能存储字符串格式,因此需要把对象转换为字符串JSON.stringify(data)
- 注意点2:获取本地存储数据,需要把里面的字符转换为对象格式JSON.parse()我们才能使用里面的数据
toDoList按下回车把新数据添加到本地存储里面
- 切记:页面中的数据,都要从本地存储里面获取,这样刷新页面不会丢失数据,所以先要把数据保存到本地存储里面
- 利用事件对象.keyCode判断用户按下回车键(13)
- 声明一个数组,保存数据
- 先要把读取本地存储原来的数据(声明函数getData()),放到这个数组里面
- 之后把最新从表单获取过来的数据,追加到数组里面
- 最后把数组存储给本地存储(声明函数setDate())
toDoList本地存储数据渲染加载到页面
- 因为后面也会经常渲染加载操作,所以声明一个函数load,方便后面调用
- 先要读取本地存储数据。(数据不要忘记转换为对象格式)
- 之后遍历这个数据,有几条数据,就生成几个小li添加到ol里面
- 每次渲染之前,先把原先里面ol的内容清空,然后渲染加载最新的数据
toDoList删除操作
- 点击里面的a链接,不是删除的li,而是删除本地存储对应的数据
- 核心原理:先获取本地存储数据,删除对应的数据,保存给本地存储,重新渲染列表li
- 我们可以给链接自定义属性记录当前的索引号
- 根据这个索引号删除相关的数据----数组的splice(i, 1)方法
- 存储修改后的数据,然后存储给本地存储
- 重新渲染加载数据列表
- 因为a是动态创建的,我们要写在创建a的函数内
toDoList正在进行和已完成选项操作
- 当我们点击了小的复选框,修改本地存储数据,再重新渲染数据列表
- 点击之后,获取本地存储数据
- 修改对应数据属性done为当前复选框的checked状态
- 之后保存数据到本地存储
- 重新渲染加载数据列表
- load加载函数里面,新增一个条件,如果当前数据的done为true就是已经完成的,就把列表渲染加载到ul里面
- 如果当前数据的done为false,则是待办事项,就把列表渲染加载到ol里面
toDoList统计正在进行个数和已经完成个数
- 在我们load函数里面操作
- 声明2个变量:todoCount待办个数 doneCount已完成个数
- 当进行遍历本地存储数据的时候,如果数据done为false,则todoCount++,否则doneCount++
- 最后修改相应的元素innerHTML
toDolist修改本地存储数据
- 当我们点击了li,就让当前小li中的p隐藏,插入input标签在p后面并把p元素内的元素给input的value
- 循环当前小li中所有的input标签判断个数,小于2的话就插入input防止重复插入
- 利用focus()方法自动获取input焦点,selectionEnd = 文本框内容的长度,将文本框内光标定位于最后
- 将修改之后的内容给本地存储对应的数据并保存到本地存储
<!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>ToDoList—最简单的待办事项列表</title>
<meta name="description" content="ToDoList无须注册即可使用,数据存储在用户浏览器的html5本地数据库里,是最简单最安全的待办事项列表应用!">
<link rel="stylesheet" href="css/todolist.css">
<script src="js/todolist.js"></script>
</head>
<body>
<header>
<section>
<form action="javascript:;" id="form">
<label for="title">ToDoList</label>
<input type="text" id="title" name="title" placeholder="添加ToDo" required="required"
autofocus="autofocus" maxlength="40" autocomplete="off">
</form>
</section>
</header>
<section>
<h2>正在进行 <span id="todocount"></span></h2>
<ol id="todolist" class="demo-box">
</ol>
<h2>已经完成 <span id="donecount"></span></h2>
<ul id="donelist">
</ul>
</section>
<footer>
Copyright © 2014 todolist.cn <a href="javascript: void(0);" class="clear">clear</a>
</footer>
</body>
</html>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #cdcdcd;
font-family: 'Courier New', Courier, monospace;
}
button,
input {
border: 0;
}
label {
cursor: pointer;
}
header {
width: 100vw;
height: 52px;
background-color: #323232;
}
header section {
color: #ddd;
font-size: 24px;
}
header section input {
float: right;
width: 60%;
height: 27px;
padding-left: 6px;
margin-top: 13px;
border-radius: 6px;
border: 1px solid #ccc;
box-shadow: 0 0 6px -2px rgba(0, 0, 0, .9) inset;
outline: none;
}
section {
width: 650px;
height: 52px;
line-height: 52px;
margin: 0 auto;
}
li {
overflow: hidden;
text-overflow: ellipsis;
position: relative;
width: 100%;
height: 40px;
line-height: 40px;
font-size: 16px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
cursor: move;
list-style: none;
margin-top: 10px;
box-shadow: 0 6px 6px -1px rgba(0, 0, 0, .2);
color: #666;
}
li::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 6px;
height: 40px;
background-color: #629a9c;
border-radius: 4px 0 0 4px;
border: 1px solid #ccc;
}
li input {
position: absolute;
top: 8px;
left: 24px;
width: 24px;
height: 24px;
cursor:pointer;
opacity: .6;
outline: none;
}
li input[type='text'] {
width: 64%;
height: 24px;
font-size: 14px;
font-family: 'Courier New', Courier, monospace;
color: #000;
border: 1px solid #4f4f4f;
border-radius: 4px;
margin-left: 40px;
padding: 8px;
}
li p {
margin-left: 72px;
}
li a {
position: absolute;
top: 7px;
right: 8px;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
color: #fff;
font-size: 14px;
font-weight: bold;
background-color: #999;
border-radius: 50%;
text-decoration: none;
}
#donelist li {
background-color: #e6e6e6;
color: #676767;
opacity: .9;
}
section h2 {
position: relative;
margin: 20px 0;
}
#todocount,
#donecount {
position: absolute;
top: 16px;
right: 0;
width: 24px;
height: 24px;
line-height: 22px;
text-align: center;
font-size: 12px;
color: #666;
background-color: #e6e6fa;
border: 1px solid #ccc;
border-radius: 50%;
}
#donelist li::before {
background-color: #b3b3b3;
}
footer {
position: fixed;
top: 90%;
left: 50%;
margin-top: 20px;
color: #666;
font-size: 14px;
transform: translateX(-50%);
}
footer a {
text-decoration: none;
color: #999;
}
window.addEventListener('load', function () {
var ipt = document.querySelector('input') // 获取input输入框
var ol = document.querySelector('#todolist') // 获取正在进行下面的ol
var ul = document.querySelector('#donelist') // 获取已经完成下面的ul
var ToDoList = document.querySelector('label') // 获取ToDoList
console.log(ToDoList);
// 获取正在进行数量统计
var todocount = document.querySelector('#todocount')
// 获取已经完成数量统计
var donecount = document.querySelector('#donecount')
// 获取clear清除所有数据链接
var clear = document.querySelector('.clear')
// 定义全局变量
var index = 0;
var li = null;
var checkbox = null;
var p = null;
var as = null;
var text = null;
// 禁止用户选中
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
// 点击ToDoList之后刷新页面
ToDoList.addEventListener('click', function() {
location.reload()
})
// 页面加载完毕时调用一次页面渲染函数
load()
ipt.addEventListener('keyup', function (e) {
// 判断用户是否按下了回车键
if (e.keyCode === 13) {
// 判断输入框内容是否为空
if (ipt.value !== '') {
// 获取本地存储的数据
var data = getDate('todolist')
// console.log(data);
// 将输入框中的内容取过来插入给data
data.push({ title: this.value, done: false })
// console.log(data);
// 将data里面的数据保存进本地存储
setDate('todolist', data)
// 重新渲染页面
load()
// 清空输入框内容
ipt.value = ''
} else {
// 如果为空弹出警示框, 提醒用户输入内容
alert('请输入内容啦!笨蛋!')
}
}
})
// 清除本地存储所有数据
clear.addEventListener('click', function () {
localStorage.clear()
// 重新渲染页面
load()
})
// 读取本地存储数据函数
function getDate(name) {
var data = localStorage.getItem(name)
// console.log(data);
if (data !== null) {
return JSON.parse(data)
} else {
return []
}
}
// 保存本地存储数据函数
function setDate(name, ele) {
localStorage.setItem(name, JSON.stringify(ele))
}
// 页面渲染函数
function load() {
// 获取本地存储的数据
var date = getDate('todolist')
// 声明变量统计正在进行的数量
var todoCount = 0;
// 声明变量统计已经完成的数量
var doneCount = 0;
// console.log(date);
// 每次生成元素渲染页面时, 清空ol和ul里面的内容
ol.innerHTML = ''
ul.innerHTML = ''
// 动态生成元素函数
function generate(ele) {
// 动态生成li、input、p以及a标签
li = document.createElement('li')
checkbox = document.createElement('input');
p = document.createElement('p')
as = document.createElement('a')
// 为每个a链接的href做赋值操作
as.href = 'javascript: void(0);';
as.innerHTML = '×' // 每个a链接的内容设置为×
// 默认隐藏a
as.style.display = 'none'
// 将本地存储数组对象中所有title属性的值赋值给p
p.innerHTML = date[i].title
// 设置input的样式为复选框
checkbox.type = 'checkbox'
// 将复选框插入到li的最前面
li.insertBefore(checkbox, li.children[0])
// 将p插入到复选框的后面
li.insertBefore(p, li.children[1])
// 将a插入到li的最后面
li.appendChild(as)
// 将生成的li插入到ele里面
ele.insertBefore(li, ele.children[0])
}
for (var i = 0; i < date.length; i++) {
// console.log(date[i].done);
// generate(ol)
// 判断本地存储数据中的done是否为true, 如果是就生成元素到ul(已经完成)里面, 并且修改复选框为选定状态和让已经完成数量+1
if (date[i].done) {
generate(ul)
checkbox.checked = true
doneCount++
} else {
// 反之, 则生成元素到ol(正在进行)中也让正在进行数量统计加1
generate(ol)
todoCount++
}
// 给a设置自定义属性
as.setAttribute('data-index', i)
// 为动态生成的a链接绑定点击事件, 注意一定要写在创建a链接的for循环里
as.addEventListener('click', function () {
// 得到当前a链接的索引号
index = this.getAttribute('data-index')
// console.log(index);
// 删除当前a链接对应的本地存储数据
date.splice(index, 1)
// 将删除后的数组重新赋值给本地存储
// console.log(date[index]);
setDate('todolist', date)
// 重新渲染页面
load()
})
// 为动态生成的复选框绑定点击事件, 注意点同上面a链接
checkbox.addEventListener('change', function () {
// console.log(this.checked);
// console.log(date);
// 得到当前a链接的索引号
var index = this.nextElementSibling.nextElementSibling.getAttribute('data-index')
// console.log(index);
// console.log(date[index].done);
// 将对应本地存储数据的done属性修改为当前复选框的选定状态
date[index].done = this.checked
setDate('todolist', date)
load()
})
// 给每个li标签绑定点击事件
li.addEventListener('click', function () {
// 创建input标签
text = document.createElement('input')
// 获取当前li标签所在的a的索引号, 从而得到li的索引
var index = this.children[2].getAttribute('data-index')
// 获取当前li里面的p元素
var p = this.children[1];
// 获取当前li里面所有的input标签
var texts = this.querySelectorAll('input');
// 判断input标签个数, 如果小于2则像li里面插入text
if (texts.length < 2) {
this.insertBefore(text, this.children[2])
}
// console.log(p);
// console.log(index);
// 将p元素的内容赋值给text的value
text.value = p.innerHTML
// 设置input样式为文本框
text.type = 'text'
// 自动获取焦点
text.focus()
// 将input内部光标定位到input内容部分最后面
text.selectionStart = text.selectionEnd = text.value.length; // 这一步虽然是多余的但是是本着练习语法的心态写上的,欸嘿~
// 默认全选中input内容
// text.select();
// this.replaceChild(text, p) // text替换p元素
// 隐藏p元素
p.style.display = 'none'
// 选中内容从最前面到内容长度的一半位置
// text.setSelectionRange(0, text.value.length / 2)
text.addEventListener('blur', function () {
// 如果当前的表单的值不为空就修改数据
if (this.value != '') {
date[index].title = this.value;
// 将修改之后的数据插入本地存储
setDate('todolist', date)
load()
} else {
alert('请输入内容啦!笨蛋!');
load();
}
})
// 文本框绑定键盘事件
text.addEventListener('keyup', function (e) {
// 用户按下回车就执行以下代码
if (e.keyCode === 13) {
// 将修改之后的数据插入本地存储
setDate('todolist', date)
load();
}
})
})
// 鼠标经过li显示a
li.addEventListener('mouseover', function () {
this.lastElementChild.style.display = 'block'
})
// 鼠标经过li隐藏a
li.addEventListener('mouseout', function () {
this.lastElementChild.style.display = 'none'
})
}
// 将计算好的数量统计分别赋值给正在进行和已经完成
donecount.innerHTML = doneCount
todocount.innerHTML = todoCount
}
})
数据可视化项目
1. 什么是数据可视化
1.1 数据可视化
- 数据可视化主要目的:借助于图形化手段,清晰有效地传达与沟通信息。
- 数据可视化可以把数据从冰冷的数字转换成图形,揭示蕴含在数据中的规律和道理。
1.2 数据可视化的场景
目前互联网公司通常有这么几个大类的可视化需求:
- 通用报表
- 移动端图表
- 大屏可视化
- 图编辑&图分析
- 地里可视化
1.3 常见的数据可视化库
- D3.js 目前Web端评价最高的JavaScript可视化工具库(入手难)
ECharts.js 百度出品的一个开源JavaScript数据可视化库
Highcharts.js 国外的前端数据可视化库,非商用免费,被许多国外大公司所使用
- AntV 蚂蚁金服全新一代数据可视化解决方案
- 等等
Highcharts和ECharts就像是Office和WPS的关系
1.4 小结
- 数据可视化主要目的:借助于图形化手段,清晰有效地传达与沟通信息
- 数据可视化在我们互联网公司中经常用于通用数据报表,移动端图表,大屏可视化,图编辑等
- 数据可视化库有很多,接下来我们重点学习ECharts
2. 数据可视化项目概述
2.1 项目目的
市场需求:
应对现在数据可视化的趋势,越来越多企业需要在很多场景(营销数据,生成数据,用户数据)下使用,**可视化图标
**来展示提现数据,让数据更加直观,数据特点更加突出
学习阶段需求:
项目对我们同学来说,起着**承上启下
**的作用。
承上
- 复习以前学习内容
- HTML5 + CSS3 布局相关技术
- JavaScript \ jQuery 相关技术
启下
- 为学习服务器编程做铺垫
- 如果把服务器里面的数据渲染到页面中?
2.3 项目技术
- HTML5 + CSS3 布局
- CSS3动画、渐变
- flex布局和rem适配方案
图片边框border-image
ES6模板字符
ECharts可视化库等等
2.4 小结
- 数据可视化项目展示
- 学习这个项目的目的:市场需求和学习阶段需求
- 项目用到的技术:以前学习过的技术和新技术
- CSS3动画、渐变
- jQuery库 + 原生JavaScript
- flex布局和rem适配方案
图片边框border-image
ES6模板字符
ECharts可视化库等等
3. ECharts简介
ECharts是一个使用JavaScript实现的开源可视化库,可以流畅的运行在pc和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图标。
官网地址:
https://www.echartsjs.com/zh/index.html
官网地址:
https://echarts.apache.org
- 丰富的可视化类型
- 多种数据格式支持
- 流数据的支持
- 移动端优化
- 跨平台使用
- 绚丽的特效
- 详细的文档说明
4. ECharts的基本使用
4.1 ECharts使用五步曲
注意:这里只要求记住ECharts使用的步骤,具体修改定制稍后会讲。
-
步骤一:下载并引入echarts.js文件 ——————-> 图标依赖这个
js库
-
步骤二:准备一个具备大小的DOM容器 ———————-> 生成的图标会放入这个
容器
内 -
步骤三:初始化echarts实例对象 ————————–>
实例化
echarts对象 -
步骤四:指定配置项和数据(option)————————–> 根据具体需求修改
配置
选项 -
步骤五:将配置设置得echarts实例对象 ———————–> 让echarts对象根据修改好的配置
生效
-
准备一个具备大小的DOM容器
<div id="main" style="width: 600px; height: 400px;"></div>
- 初始化echarts实例对象
var myChart = echarts.init(document.getElementById('main'));
- 指定配置项和数据
// 指定图表的配置项和数据
var option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
};
4.3 相关配置讲解
- title:标题组件
- tooltip:提示框组件
- legend:图例组件
- grid:直角坐标系内绘图网格
- XAxis:直角坐标系grid中的x轴
- yAxis:直角坐标系grid中的y轴
- series:系列列表。每个系列通过type决定自己的图表类型(什么类型的图表)
- color:调色盘颜色列表
先了解以上9个配置的作用,其余配置还有具体细节通过查阅文档:文档菜单-配置项手册
学echarts关键在于学会查阅文档,根据需求修改配置
grid: {
left: '0%',
right: '0%',
bottom: '3%',
// 当刻度标签溢出的时候,grid区域是否包含坐标轴的刻度标签。如果为true则显示
// 如果left right等设置为0%,刻度标签就溢出了,此时决定是否显示刻度标签
containLabel: true
}
series:系列列表
-
type:类型(什么类型的图表)比如line是折线bar柱形等
-
name:系列名称,用于tooltip的显示,legend的图例筛选变化
-
stack:数据堆叠。如果设置相同值,则会数据堆叠。
-
数据堆叠:第二个数据值 = 第一个数据值 + 第二个数据值
第三个数据值 = 第二个数据值 + 第三个数据值…. 依次叠加
如果给stack指定不同值或者去掉这个属性则不会发生数据堆叠
-
5. 数据可视化项目适配方案
5.1 项目需求
- 设计稿是1920px
- PC端适配:宽度在1024~1920之间页面元素宽高自适应
5.2 适配方案
flexible.js
检测浏览器宽度
修改html文字大小
rem单位
页面元素根据rem适配大小
配合cssrem插件
flex布局
页面快速布局
- flexible.js把屏幕分为24等份
- PC端的效果图是1920px
- cssrem插件的基准值是80px
- rem值自动生成
要把屏幕约束在1024~1920之间有适配
@media screen and (max-width: 1024px) {
html {
font-size: 42.66px!important;
}
}
@media screen and (min-width: 1920px) {
html {
font-size: 80px!important;
}
}
6. 数据可视化项目开发
6.1 边框图片
- 边框图片的使用场景
- 边框图片的切图原理
- 边框图片语法规范
盒子大小不一,但是边框样式相同,此时就需要边框图片来完成
为了实现丰富多彩的边框效果,在CSS3中,新增了border-image属性,这个新属性允许指定一幅图像作为元素的边框。
1. 边框图片切图原理:(重要)
把四个角切出去(九宫格的由来),中间部分可以铺排,拉伸或者环绕。
2. 边框图片语法:(重要)
属性 | 描述 |
---|---|
border-image-source | 用在边框的图片的路径。(哪个图片?) |
border-image-slice | 图片边框向内偏移。(裁剪的尺寸,一定不加单位,上右下左顺序) |
border-image-width | 图片边框的宽度(需要加单位)(不是边框的宽度,是边框图片的宽度) |
border-image-repeat | 图像边框是否应平铺(repeat)、铺满(round)或拉伸(stretch)默认拉伸 |
6.2 公共面板样式开发
面板类:.panel
border-image-slice:按照 上右下左顺序切割
/* 公共面板样式 */
.panel {
position: relative;
border: 15px solid transparent;
border-width: .6375rem .475rem .25rem 1.65rem;
border-image-source: url(../images/border.png);
border-image-slice: 51 38 20 132;
margin-bottom: .25rem;
}
.panle h3 {
color: #fff;
font-size: .35rem;
margin-bottom: .15rem;
}
.inner {
position: absolute;
top: -.6375rem;
right: -.475rem;
bottom: -0.25rem;
left: -1.65rem;
padding: .3rem .45rem;
}
6.3 通过类名调用字体图标
- HTML页面引入字体图标中css文件。
- 标签直接调用图标对应的类名即可。(类名在css文件中标注)
引入css文件和声明字体图标的时候,一定注意路径问题。
6.4 立即执行函数用法
JS文件中,会有大量的变量命名,特别是ECharts使用中,需要大量初始化ECharts对象?
为了防止变量名冲突(变量污染)我们采用立即执行函数策略:
(function () {}) ();
(function () {
var num = 10;
}) ();
(function () {
var num = 10;
}) ();
6.5 无缝滚动原理
- 先克隆marquee里面所有的行(row)
- 通过css3动画滚动marquee
- 鼠标经过marquee就停止动画
animation-play-state: paused;
// 先克隆marquee里面所有的行(row)
var rows = null;
var newRows = null;
for (var i = 0; i < marquees.length; i++) {
rows = marquees[i].querySelectorAll('.row')
for (var j = 0; j < rows.length; j++) {
newRows = rows[j].cloneNode(true);
marquees[i].appendChild(newRows);
}
}
.marquee-view .marquee {
position: absolute;
display: flex;
flex-direction: column;
width: 100%;
animation: move 15s linear infinite;
}
/* 通过css3动画滚动marquee */
@keyframes move {
from {}
to {
transform: translateY(-50%);
}
}
/* 鼠标经过marquee就停止动画 */
.marquee-view .marquee:hover {
animation-play-state: paused;
}
6.6 点位分析模块-使用ECharts图表
- 先官网找到类似的图表引入到页面中
- 根据需求修改具体的配置
1. 引入图表
// 点位分布统计模块
(function() {
// 1. 实例化ECharts对象
var myCharts = echarts.init(document.querySelector('.pie'));
// 2. 指定配置项和数据
var option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
series: [
{
name: 'Area Mode',
type: 'pie',
radius: [20, 140],
center: ['75%', '50%'],
roseType: 'area',
itemStyle: {
borderRadius: 5
},
data: [
{ value: 30, name: 'rose 1' },
{ value: 28, name: 'rose 2' },
{ value: 26, name: 'rose 3' },
{ value: 24, name: 'rose 4' },
{ value: 22, name: 'rose 5' },
{ value: 20, name: 'rose 6' },
{ value: 18, name: 'rose 7' },
{ value: 16, name: 'rose 8' }
]
}
]
};
// 3. 配置项和数据给实例化对象
myCharts.setOption(option);
}) ();
2. 定制图表
第一步:参考官方列子,熟悉里面参数具体含义
var option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
series: [
{
name: 'Area Mode',
type: 'pie',
radius: [20, 140],
center: ['75%', '50%'],
roseType: 'area',
itemStyle: {
borderRadius: 5
},
data: [
{ value: 30, name: 'rose 1' },
{ value: 28, name: 'rose 2' },
{ value: 26, name: 'rose 3' },
{ value: 24, name: 'rose 4' },
{ value: 22, name: 'rose 5' },
{ value: 20, name: 'rose 6' },
{ value: 18, name: 'rose 7' },
{ value: 16, name: 'rose 8' }
]
}
]
};
第二步:按照需求定制
- 需求1:颜色设置
color : ['#006cff', '#60cda0', '#ed8884', '#ff9f7f', '#0096ff', '#32c5e9', '#1d9dff']
- 需求2:修改饼形图大小(series对象)
radius: ['10%', '70%'],
- 需求3:数据使用更换(series对象里面data对象)
{ value: 20, name: '云南' },
{ value: 26, name: '北京' },
{ value: 24, name: '山东' },
{ value: 25, name: '河北' },
{ value: 20, name: '江苏' },
{ value: 25, name: '浙江' },
{ value: 30, name: '四川' },
{ value: 42, name: '湖北' }
- 需求4:字体略小些 10px (series对象里面设置)
饼图图形上的文本标签可以控制饼形图的文字的一些样式。 label对象设置
series: [
{
// 图表名称
name: '点位统计',
// 图表类型
type: 'pie',
// 南丁格尔玫瑰图有两个圆,内圆半径10%,外圆半径70%
// 饼形图半径。可以是像素,也可以是百分比(基于图表bom容器的半径 第一项是内半径,第二项是外半径)
radius: ['10%', '70%'],
// 图表中心位置left 50% top 50% 距离图表DOM容器
center: ['50%', '50%'],
// radius 半径模式,另外一种是area面积模式
roseType: 'radius',
// 数据集 value 数据的值 name 数据的名称
data: [
{ value: 20, name: '云南' },
{ value: 26, name: '北京' },
{ value: 24, name: '山东' },
{ value: 25, name: '河北' },
{ value: 20, name: '江苏' },
{ value: 25, name: '浙江' },
{ value: 30, name: '四川' },
{ value: 42, name: '湖北' }
],
// 文本标签控制饼形图文字的相关样式,注意它是一个对象
label: {
fontSize: 10
}
}
]
- 需求5:防止缩放的时候,引导性过长。引导线略短些(series对象里面的labelLine对象设置)
- 连接图表6px
- 连接文字8px
// 引导线调整
labelLine: {
// 连接扇形图线长
length: 6,
// 连接文字线长
length2: 8
}
6.7 ES6模板文字
var star = {
name: "刘德华",
age: 18
};
// 以前的写法 拼接的时候引号很容易出问题
console.log("我的名字是" + star.name + "我的年龄是" + star.age);
// ES6 模板字符写法
console.log(`我的名字是${star.name}
我的年龄是${star.age}`);
console.log(`<span>${star.name}</span><span>${star.age}</span>`);
</script>