本篇为 JavaScript 系列笔记第八篇,将陆续更新后续内容。参考:黑马程序员JavaScript核心教程,前端基础教程
系列笔记:
JavaScript(一)—— 初识JavaScript / 注释 / 输入输出语句 / 变量 / 数据类型
JavaScript(二)—— 运算符 / 流程控制 / 数组
JavaScript(三)—— 函数 / 作用域 / 预解析 / 对象
JavaScript(四)—— 内置对象 / 简单数据类型与复杂类型
JavaScript(五)—— Web APIs 简介 / JavaScript 必须掌握的 DOM 操作
JavaScript(六)—— DOM 事件高级
JavaScript(七)—— BOM 浏览器对象模型
offset 系列相关属性可以 动态的 得到该元素的位置(偏移)、大小等:
offsetTop
、 offsetLeft
属性offsetWidth
、 offsetHeight
属性padding
、border
、width
offsetParent
属性parentNode
相比,parentNode
返回最近一级的父亲,无论父亲是否有定位offset
style
var box = document.querySelector('.box');
box.addEventListener('mousemove', function (e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
this.innerHTML = 'x坐标是' + x + ' y坐标是' + y;
})
box.addEventListener('mouseout', function () {
this.innerHTML = '';
})
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');
// 显示
link.addEventListener('click', function () {
mask.style.display = 'block';
login.style.display = 'block';
})
// 隐藏
closeBtn.addEventListener('click', function () {
mask.style.display = 'none';
login.style.display = 'none';
})
// 拖拽
title.addEventListener('mousedown', function (e) {
var x = e.pageX - login.offsetLeft;
var y = e.pageY - login.offsetTop;
document.addEventListener('mousemove', move);
function move(e) {
login.style.left = e.pageX - x + 'px';
login.style.top = e.pageY - y + 'px';
}
document.addEventListener('mouseup', function () {
document.removeEventListener('mousemove', move);
})
})
mousedown
,获取鼠标在盒子中坐标mousemove
,求得模态框的 left
、top
mouseup
,移除注册事件 removeEventListener
部分 HTML 代码
<div class="preview_img">
<img src="img/mac_small.jpg" alt=""> <--小图-->
<div class="mask">div> <--遮罩-->
<div class="preview_img_big"> <--大图-->
<img src="img/mac_big.jpg" alt="" class="big_img">
div>
div>
部分 CSS 代码
.preview_img {
position: relative;
height: 450px;
border: 1px solid #ccc;
}
.mask {
display: none;
position: absolute;
left: 0;
top: 0;
width: 300px;
height: 300px;
background: #FEDE4F;
opacity: .5; /* 不透明度 */
border: 1px solid #ccc;
cursor: move; /* 鼠标样式为移动 */
}
.preview_img_big {
display: none;
position: absolute;
left: 450px;
top: -1px;
width: 540px;
height: 540px;
border: 1px solid #ccc;
overflow: hidden;
}
.big_img {
position: absolute;
top: 0;
left: 0;
}
JS 代码
window.addEventListener('load', function () {
var preview_img = document.querySelector('.preview_img');
var mask = document.querySelector('.mask');
var big = document.querySelector('.preview_img_big');
preview_img.addEventListener('mouseover', function () {
mask.style.display = 'block';
big.style.display = 'block';
})
preview_img.addEventListener('mousemove', move);
function move(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
// 减去遮罩盒子高度一半
var maskX = x - mask.offsetWidth / 2;
var maskY = y - mask.offsetHeight / 2;
// 最大移动距离(宽高相等)
var maskMax = preview_img.offsetWidth - mask.offsetWidth;
if (maskX <= 0) {
maskX = 0;
} else if (maskX >= maskMax) {
maskX = maskMax;
}
if (maskY <= 0) {
maskY = 0;
} else if (maskY >= maskMax) {
maskY = maskMax;
}
mask.style.left = maskX + 'px';
mask.style.top = maskY + 'px';
// 获得大图片
var big_img = document.querySelector('.big_img');
// 最大移动距离
var bigMax = big_img.offsetWidth - big.offsetWidth;
var bigX = maskX * bigMax / maskMax;
var bigY = maskY * bigMax / maskMax;
big_img.style.left = -bigX + 'px';
big_img.style.top = -bigY + 'px';
}
preview_img.addEventListener('mouseout', function () {
mask.style.display = 'none';
big.style.display = 'none';
})
})
注意:
window.addEventListener('load', function(){})
,否则注册事件绑定为空,出现报错通过 client 相关属性可以动态的得到该元素的边框大小、元素大小
使用 scroll 相关属性可以动态得到该元素的大小、滚动距离等
onscroll
事件如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条,当滚动条向下滚动时,会触发 onscroll
事件
JS 代码
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
var bannerTop = banner.offsetTop;
var sliderbarTop = sliderbar.offsetTop - bannerTop;
var main = document.querySelector('.main');
var goBack = document.querySelector('.goBack');
var mainTop = main.offsetTop;
document.addEventListener('scroll', function (e) {
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
if (window.pageYOffset >= mainTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
注意:页面被卷去的头部,有兼容问题,通常有下面几种写法:
),使用 document.documentElement.scrollTop
document.body.scrollTop
window.pageYOffset
、 window.pageXOffset
通过定时器 setInterval()
不断移动盒子位置。实现步骤:
可能一个页面中会多次调用动画过程,因此可以将其封装成函数。
注意:函数需要传递 2 个参数,动画对象 和 移动的距离
function animate(obj, target) {
var timer = setInterval(function () {
if (obj.offsetLeft >= target) {
clearInterval(timer);
}
obj.style.left = obj.offsetLeft + 2 + 'px';
}, 10)
}
若每次调用都声明 var timer
变量,会造成占用大量内存以及重复命名歧义等问题。因此,这里利用给对象添加属性的方式进行赋值操作 obj.timer
,实现了不同元素指定不同定时器
return
结束函数调用缓动动画就是让元素运动速度有所变化,最常见的是逐渐降速到停止,使得效果更加自然
算法: 步长 = (目标位置 - 当前位置)/ 10
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
回调函数原理:函数可以作为一个参数,作为传递到另一个函数中
function animate(obj, target, callback) {
} // callback 回调函数
animate(span, 500, function () {
});
以后会经常使用动画函数,因此可以将其单独封装到一个 JS 文件中,使用时直接引用 JS 文件即可
<div class="sliderbar">
<span>←span>
<div class="con">问题反馈div>
div>
<script>
var sliderbar = document.querySelector('.sliderbar');
var con = document.querySelector('.con');
sliderbar.addEventListener('mouseenter', function () {
animate(con, -160, function () {
sliderbar.children[0].innerHTML = '→';
});
})
sliderbar.addEventListener('mouseleave', function () {
animate(con, 0, function () {
sliderbar.children[0].innerHTML = '←';
});
})
script>
mouseenter
、mouseleave
和 mouseover
、mouseout
用法一样,区别在于前者无法冒泡function animate(obj, target, callback) {
clearInterval(obj.timer);
obj.timer = setInterval(function () {
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
clearInterval(obj.timer);
callback && callback();
}
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
window.addEventListener('load', function () {
var arrow_l = document.querySelector('.arrow-l');
var arrow_r = document.querySelector('.arrow-r');
var focus = document.querySelector('.focus');
var focusWidth = focus.offsetWidth;
// 鼠标经过 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);
})
// 动态生成小圆圈
var ul = focus.querySelector('ul');
var ol = focus.querySelector('.circle');
for (var i = 0; i < ul.children.length; i++) {
var li = document.createElement('li');
// 淘宝源码中 li 中又创建了 a,其实在这里不创建 a 也可以,本案例暂且依淘宝为准
var a = document.createElement('a');
// 记录索引
a.setAttribute('index', i);
ol.appendChild(li);
li.appendChild(a);
// 圆圈绑定事件
li.addEventListener('focus', function () {
this.blur();
})
a.addEventListener('click', function () {
// 排他思想,被点击的圆圈设置 current 样式
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].firstChild.className = '';
}
this.className = 'current';
// 移动图片
var index = this.getAttribute('index');
animate(ul, - index * focusWidth);
// 修改索引
num = circle = index;
})
}
ol.children[0].firstChild.className = 'current';
// 克隆第一张图片放在 ul 最后,实现无缝滚动(不复制直接添加的话会多出一个小圆圈)
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
// 点击右侧按钮, 图片滚动一张
var num = 0;
// circle 控制小圆圈的播放
var circle = 0;
// flag 节流阀, 防止连续点击造成过快播放
var flag = true;
// 右侧按钮
arrow_r.addEventListener('click', function () {
if (flag) {
flag = false; // 关闭节流阀
// 如果走到最后复制的一张图片,ul 快速复原
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * focusWidth, function () {
flag = true; // 打开节流阀
});
circle++;
// 最后一张时复原
if (circle == ol.children.length) {
circle = 0;
}
// 调用函数
circleChange();
}
})
// 左侧按钮
arrow_l.addEventListener('click', function () {
if (flag) {
flag = false;
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = - num * focusWidth + 'px';
}
num--;
animate(ul, -num * focusWidth, function () {
flag = true;
});
circle--;
circle = circle < 0 ? ol.children.length - 1 : circle;
circleChange();
}
})
// 圆圈变色
function circleChange() {
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].firstChild.className = '';
}
ol.children[circle].firstChild.className = 'current';
}
// 自动播放轮播图
var timer = this.setInterval(function () {
arrow_r.click();
}, 2000);
})
遇到的问题
在此前案例中,返回顶部使用的是锚点链接,在此处介绍一种新方式:
window.scroll(x, y) // 滚动窗口至文档中的指定位置
本案例利用 window.scroll(x, y)
配合动画函数 animate
实现动画返回顶部效果
部分 JS 代码
goBack.addEventListener('click', function () {
// 窗口进行滚动, 对象是 window
animate(window, 0);
// window.scroll(0, 0);
});
// 动画函数
function animate(obj, target) {
clearInterval(obj.timer);
obj.timer = setInterval(function () {
var step = (target - window.pageYOffset) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (window.pageYOffset == target) {
clearInterval(obj.timer);
}
window.scroll(0, window.pageYOffset + step);
}, 15);
}
这里注意 animate 函数 将参数改为垂直相关,利用 window.pageYOffset
window.addEventListener('load', function () {
var cloud = document.querySelector('.cloud');
var c_nav = document.querySelector('.c-nav');
var lis = c_nav.querySelectorAll('li');
// 设置变量,记录起始位置
var current = 0;
for (var i = 0; i < lis.length; i++) {
// 鼠标进入,当前 li 位置为目标值
lis[i].addEventListener('mouseenter', function () {
animate(cloud, this.offsetLeft);
})
// 鼠标离开,回到起始位置
lis[i].addEventListener('mouseleave', function () {
animate(cloud, current);
})
// 鼠标点击,当前位置设为目标值
lis[i].addEventListener('click', function () {
current = this.offsetLeft;
})
}
})