一时拖更一时爽,一直拖更一直爽。。。
再拖就掉粉了。。。
赶紧爬起来更新。。。
今天做了好些个案例,要求比较全面,要有合理的布局,扎实的js基础,熟练的DOM操作,这些我都没有 。。。OvO,写的很小学生水平,看个乐就好,如有错误,恳请指正。
一. 模拟手机验证码
要实现下图的功能点,验证手机号码,判断号码格式,以及倒计时再发送。
手机号码的校验可以使用正则匹配
let btn = document.querySelector(".btn");
let inp = document.querySelector(".inp");
let timer = null;
let count = 5;
btn.addEventListener("click", () => {
let reg = /^1[3-9]\d{9}$/;
if (inp.value == "") {
alert("要输入手机号 OvO ");
return;
}
if (reg.test(inp.value)) {
// console.log(1);
fn();
} else {
alert("手机号格式不正确 OvO ");
return;
}
});
要实现倒计时功能,必须保证定时器在运行时不能被打断,不能被重新赋值。
我的解决思路是,没有定时器即 timer 为空,取反时添加定时器,定时器不停止,timer 不清空时,!timer 为false,是不能再添加定时器的,这样就能解决了。
function fn() {
if (!timer) {
timer = setInterval(() => {
count--;
btn.classList.toggle("active");
btn.innerHTML = `${count}秒后可再次发送`;
// console.log(count);
if (count <= 0) {
btn.classList.toggle("active");
clearInterval(timer);
count = 5;
btn.innerHTML = `点击发送验证码`;
}
}, 1000);
}
}
完整代码:
<!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 {
display: flex;
height: 60px;
align-items: center;
}
.format {
width: 100px;
height: 32px;
border-radius: 5px;
background-color: skyblue;
text-align: center;
line-height: 32px;
margin-right: 20px;
}
.inp {
width: 150px;
height: 30px;
border-radius: 5px;
border: 1px solid black;
margin-right: 20px;
padding: 0 10px;
outline: 0;
}
.btn {
width: 120px;
height: 32px;
border-radius: 5px;
background-color: skyblue;
border: 0;
cursor: pointer;
}
.active {
background-color: rgb(175, 225, 245);
}
</style>
</head>
<body>
<div class="box">
<div class="format">中国 +86</div>
<label for="">手机号码:</label>
<input type="text" class="inp" />
<button class="btn">点击发送验证码</button>
<script>
let btn = document.querySelector(".btn");
let inp = document.querySelector(".inp");
let timer = null;
let count = 5;
btn.addEventListener("click", () => {
let reg = /^1[3-9]\d{9}$/;
if (inp.value == "") {
alert("要输入手机号 OvO ");
return;
}
if (reg.test(inp.value)) {
// console.log(1);
fn();
} else {
alert("手机号格式不正确 OvO ");
return;
}
});
function fn() {
if (!timer) {
timer = setInterval(() => {
count--;
btn.classList.toggle("active");
btn.innerHTML = `${count}秒后可再次发送`;
// console.log(count);
if (count <= 0) {
btn.classList.toggle("active");
clearInterval(timer);
count = 5;
btn.innerHTML = `点击发送验证码`;
}
}, 1000);
}
}
</script>
</div>
</body>
</html>
二.模拟滑块验证码
实现以下功能,鼠标按下滑块并移动时,滑块跟随移动,验证码页随之移动,最后重叠误差小于4像素时判定验证成功。
右侧的是遮罩层,用一个半透明盒子遮盖住背景图片的一部分,通过定位方式排版,左侧的验证滑块需要塞入整个背景图片,通过 backgroundPosition 属性来移动背景图,值就为遮罩层的定位偏移数据,主页就可以实现两个滑块显示背景图的同一部分了。
实现的一个难点是,验证块的半圆也需要添加背景图片,通过定位的方式跟验证保持一致,半圆定位 left top 值,就是背景图片需要偏移的值。
let img = document.querySelector(".img");
let mask = document.querySelector(".mask");
let square = document.querySelector(".square");
let sliding = document.querySelector(".sliding");
let sliding_box = document.querySelector(".sliding_box");
let square_after = document.querySelector(".after");
let square_before = document.querySelector(".before");
//遮罩层随机位置,只能出现在右半部分,所以需要进行限制
let ran_left = Math.floor(Math.random() * 240 + 240);
let ran_top = Math.floor(Math.random() * 205);
// console.log(ran_position);
mask.style["left"] = ran_left + "px";
mask.style["top"] = ran_top + "px";
//方块位置同高 方块对应背景图
square.style["top"] = ran_top + "px";
square.style["backgroundPosition"] = `-${ran_left}px -${ran_top}px`;
square_before.style["backgroundPosition"] = `-${ran_left - 15}px -${
ran_top + 30
}px`;
square_after.style["backgroundPosition"] = `-${ran_left + 30}px -${
ran_top - 15
}px`;
剩下的功能难点就集中在两个滑块的偏移位置了
//滑块移动
let isStop = true;
//鼠标按下时
sliding_box.addEventListener("mousedown", () => {
isStop = false;
});
//鼠标按下并移动时
sliding_box.addEventListener("mousemove", (e) => {
if (!isStop) {
//获取鼠标距离盒子的距离,减去滑块的一半宽度,使鼠标居于滑块中心点
let leftX =
e.pageX - sliding.offsetLeft - sliding_box.offsetWidth / 2;
//作两个判断,滑块移动到右端停止,移动到左端赋0
if (leftX > sliding.offsetWidth - sliding_box.offsetWidth)
leftX = sliding.offsetWidth - sliding_box.offsetWidth;
if (leftX < 0) leftX = 0;
//赋值给滑块
sliding_box.style["left"] = leftX + "px";
//方块跟随滑块移动
square.style["left"] = leftX + "px";
}
});
//鼠标抬起时
sliding_box.addEventListener("mouseup", () => {
isStop = true;
//做个绝对值判断
let left_abs = Math.abs(
parseInt(square.style["left"]) - parseInt(mask.style["left"])
);
if (left_abs < 5) {
console.log("验证成功");
} else {
console.log("验证失败,请重新验证");
square.style["left"] = 25 + "px";
sliding_box.style["left"] = 0 + "px";
}
});
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>模拟滑动验证</title>
<style>
body {
background-color: rgba(232, 232, 232, 1);
}
.img {
width: 640px;
height: 300px;
background-image: url(./img/3.jpeg);
background-repeat: no-repeat;
position: relative;
}
.mask {
width: 80px;
height: 80px;
background-color: rgba(240, 240, 240, 0.5);
position: absolute;
background-repeat: no-repeat;
top: 110px;
left: 400px;
}
.mask::after {
content: "";
width: 15px;
height: 25px;
/* background-color: pink; */
position: absolute;
top: 30px;
left: -15px;
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
background-color: rgba(240, 240, 240, 0.5);
/* background-image: url(./img/3.jpeg); */
}
.mask::before {
content: "";
width: 25px;
height: 15px;
/* background-color: pink; */
position: absolute;
top: -15px;
left: 30px;
border-top-right-radius: 15px;
border-top-left-radius: 15px;
background-color: rgba(240, 240, 240, 0.5);
/* background-image: url(./img/3.jpeg); */
}
.square {
width: 80px;
height: 80px;
background-color: rgba(240, 240, 240, 0.5);
position: absolute;
left: 25px;
background-image: url(./img/3.jpeg);
background-repeat: no-repeat;
/* background-position: -100px -100px; */
/* border: 1px solid #fff; */
}
.before {
width: 15px;
height: 25px;
position: absolute;
top: 30px;
left: -15px;
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
background-image: url(./img/3.jpeg);
}
.after {
width: 25px;
height: 15px;
position: absolute;
top: -15px;
left: 30px;
border-top-right-radius: 15px;
border-top-left-radius: 15px;
background-image: url(./img/3.jpeg);
}
.sliding {
width: 640px;
height: 40px;
background-color: rgba(240, 240, 240, 1);
margin-top: 10px;
border-radius: 10px;
position: relative;
}
.sliding_box {
width: 80px;
height: 40px;
background-color: #fff;
border-radius: 10px;
text-align: center;
line-height: 40px;
cursor: pointer;
position: absolute;
}
</style>
</head>
<body>
<div class="box">
<div class="img">
<!-- 遮罩层 -->
<div class="mask"></div>
<!-- 方块 -->
<div class="square">
<i class="before"></i>
<i class="after"></i>
</div>
</div>
<div class="sliding">
<!-- 滑块 -->
<div class="sliding_box"></div>
</div>
</div>
<script>
let img = document.querySelector(".img");
let mask = document.querySelector(".mask");
let square = document.querySelector(".square");
let sliding = document.querySelector(".sliding");
let sliding_box = document.querySelector(".sliding_box");
let square_after = document.querySelector(".after");
let square_before = document.querySelector(".before");
//遮罩层随机位置
let ran_left = Math.floor(Math.random() * 240 + 240);
let ran_top = Math.floor(Math.random() * 205);
// console.log(ran_position);
mask.style["left"] = ran_left + "px";
mask.style["top"] = ran_top + "px";
//方块位置同高 方块对应背景图
square.style["top"] = ran_top + "px";
square.style["backgroundPosition"] = `-${ran_left}px -${ran_top}px`;
square_before.style["backgroundPosition"] = `-${ran_left - 15}px -${
ran_top + 30
}px`;
square_after.style["backgroundPosition"] = `-${ran_left + 30}px -${
ran_top - 15
}px`;
//滑块移动
let isStop = true;
//鼠标按下时
sliding_box.addEventListener("mousedown", () => {
isStop = false;
});
//鼠标按下并移动时
sliding_box.addEventListener("mousemove", (e) => {
if (!isStop) {
//获取鼠标距离盒子的距离,减去滑块的一半宽度,使鼠标居于滑块中心点
let leftX =
e.pageX - sliding.offsetLeft - sliding_box.offsetWidth / 2;
//作两个判断,滑块移动到右端停止,移动到左端赋0
if (leftX > sliding.offsetWidth - sliding_box.offsetWidth)
leftX = sliding.offsetWidth - sliding_box.offsetWidth;
if (leftX < 0) leftX = 0;
//赋值给滑块
sliding_box.style["left"] = leftX + "px";
//方块跟随滑块移动
square.style["left"] = leftX + "px";
}
});
//鼠标抬起时
sliding_box.addEventListener("mouseup", () => {
isStop = true;
//做个绝对值判断
let left_abs = Math.abs(
parseInt(square.style["left"]) - parseInt(mask.style["left"])
);
if (left_abs < 5) {
console.log("验证成功");
} else {
console.log("验证失败,请重新验证");
square.style["left"] = 25 + "px";
sliding_box.style["left"] = 0 + "px";
}
});
</script>
</body>
</html>
三.模拟时钟
实现模拟时钟的功能,时钟能够展示当前时间
实现的难点不在于js,而在于布局,详情可以找找我的其他博客,有更详细的说明,想偷个懒~ OvO ~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
.box {
width: 380px;
height: 380px;
border: 20px solid pink;
border-radius: 50%;
position: relative;
}
.point {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: pink;
position: absolute;
top: 180px;
left: 180px;
z-index: 100;
}
.box ul {
list-style: none;
}
.box ul li {
height: 10px;
width: 360px;
/* border: 1px solid black; */
position: absolute;
top: 185px;
padding: 0 10px;
display: flex;
justify-content: space-between;
/* left: 180px; */
}
.box ul li::after,
.box ul li::before {
content: "";
display: block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: black;
}
.box ul li:nth-child(1) {
transform: rotate(30deg);
}
.box ul li:nth-child(2) {
transform: rotate(60deg);
}
.box ul li:nth-child(3) {
transform: rotate(90deg);
}
.box ul li:nth-child(4) {
transform: rotate(120deg);
}
.box ul li:nth-child(5) {
transform: rotate(150deg);
}
.hour,
.minute,
.second {
width: 10px;
position: absolute;
transform-origin: bottom center;
}
.hour {
height: 90px;
background-color: pink;
top: 100px;
left: 185px;
}
.minute {
height: 120px;
background-color: skyblue;
top: 70px;
left: 185px;
}
.second {
height: 150px;
background-color: beige;
top: 40px;
left: 185px;
}
</style>
</head>
<body>
<div class="box">
<div class="point"></div>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<!-- 时针 -->
<div class="hour"></div>
<!-- 分针 -->
<div class="minute"></div>
<!-- 秒针 -->
<div class="second"></div>
</div>
<script>
let hour = document.querySelector(".hour");
let minute = document.querySelector(".minute");
let second = document.querySelector(".second");
setInterval(() => {
//获取当前时分秒
let hour_time = new Date().getHours();
let minute_time = new Date().getMinutes();
let second_time = new Date().getSeconds();
// console.log(hour_time, minute_time, second_time);
//每一小时时针旋转30°,每一分钟分针旋转6°,秒钟也是6°
hour_time = hour_time + minute_time / 60; // 不足一小时占多少角度
minute_time = minute_time + second_time / 60;
hour.style["transform"] = `rotate(${hour_time * 30}deg)`;
minute.style["transform"] = `rotate(${minute_time * 6}deg)`;
second.style["transform"] = `rotate(${second_time * 6}deg)`;
}, 1000);
</script>
</body>
</html>
四. 生成随机彩票
根据输入的值,生成随机数彩票
实现思路可以是生成一个随机的二维数组,通过数组的遍历进行动态生成结构。
先声明外层数组,通过for循环生成里层,再通过第二个for循环添加元素,这样就能生成一个二维数组了。如何通过 sort( ) 方法打乱,取出前六个作为红色球的数值,最后再添加一个随机数作为蓝色球的数值,这样就可以拿到需要的数组。
function createLottery(red_max, red_min, blue_max, blue_min, sel) {
let arr_1 = [];
for (let j = 0; j < sel; j++) {
let arr_2 = [];
for (let i = 0; i < red_max; i++) {
arr_2.push(i);
//打乱数组
arr_2.sort(function () {
return Math.random() - 0.5;
});
}
arr_2 = arr_2.splice(0, 6);
arr_2.push(
Math.floor(Math.random() * (blue_max - blue_min) + blue_min)
);
arr_1.push(arr_2); //数组添加数组,形成二维数组
}
return arr_1;
}
要注意的是,获取输入框的数据类型是字符串,需要进行数值类型的转换。
最后根据二维数组的结构,进行两次循环取值渲染就OK了。
btn.addEventListener("click", () => {
list.innerHTML = "";
//数据转数组类型
let red_max_value = red_max.value - 0;
let red_min_value = red_min.value - 0;
let blue_max_value = blue_max.value - 0;
let blue_min_value = blue_min.value - 0;
let sel_value = sel.value - 0;
if (
!red_max.value ||
!red_min.value ||
!blue_max.value ||
!blue_min.value ||
!sel.value
) {
alert("请输入完整数据");
} else {
console.log("生成彩票ing");
let arr = createLottery(
red_max_value,
red_min_value,
blue_max_value,
blue_min_value,
sel_value
);
for (let i = 0; i < arr.length; i++) {
let ul = document.createElement("ul");
for (let j = 0; j < arr[i].length; j++) {
let li = document.createElement("li");
li.innerHTML = `${arr[i][j]}`;
ul.appendChild(li);
}
list.appendChild(ul);
}
}
});
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
input {
width: 50px;
height: 15px;
}
.list ul {
display: flex;
padding: 0 15px;
list-style: none;
}
.list ul li {
width: 50px;
height: 50px;
background-color: #ccc;
text-align: center;
line-height: 50px;
border-radius: 50%;
margin: 10px 10px 0 0;
}
.list ul li:nth-child(-n + 6) {
background-color: red;
color: #fff;
}
.list ul li:nth-child(7) {
background-color: deepskyblue;
color: #fff;
}
</style>
</head>
<body>
<div class="layout">
<div class="red">
红色数字 <label for="">最大值:</label><input type="text" class="max" />
<label for="">最小值:</label><input type="text" class="min" />
</div>
<div class="blue">
蓝色数字 <label for="">最大值:</label><input type="text" class="max" />
<label for="">最小值:</label><input type="text" class="min" />
</div>
<label for="">数量:</label>
<select class="sel">
<option value="2">2注</option>
<option value="5">5注</option>
<option value="10">10注</option>
</select>
<button class="btn">生成</button>
<div class="list"></div>
</div>
<script>
let red_max = document.querySelector(".red .max");
let red_min = document.querySelector(".red .min");
let blue_max = document.querySelector(".blue .max");
let blue_min = document.querySelector(".blue .min");
let sel = document.querySelector(".sel");
let btn = document.querySelector(".btn");
let list = document.querySelector(".list");
btn.addEventListener("click", () => {
list.innerHTML = "";
//数据转数组类型
let red_max_value = red_max.value - 0;
let red_min_value = red_min.value - 0;
let blue_max_value = blue_max.value - 0;
let blue_min_value = blue_min.value - 0;
let sel_value = sel.value - 0;
if (
!red_max.value ||
!red_min.value ||
!blue_max.value ||
!blue_min.value ||
!sel.value
) {
alert("请输入完整数据");
} else {
console.log("生成彩票ing");
let arr = createLottery(
red_max_value,
red_min_value,
blue_max_value,
blue_min_value,
sel_value
);
for (let i = 0; i < arr.length; i++) {
let ul = document.createElement("ul");
for (let j = 0; j < arr[i].length; j++) {
let li = document.createElement("li");
li.innerHTML = `${arr[i][j]}`;
ul.appendChild(li);
}
list.appendChild(ul);
}
}
});
function createLottery(red_max, red_min, blue_max, blue_min, sel) {
let arr_1 = [];
for (let j = 0; j < sel; j++) {
let arr_2 = [];
for (let i = 0; i < red_max; i++) {
arr_2.push(i);
//打乱数组
arr_2.sort(function () {
return Math.random() - 0.5;
});
}
arr_2 = arr_2.splice(0, 6);
arr_2.push(
Math.floor(Math.random() * (blue_max - blue_min) + blue_min)
);
arr_1.push(arr_2); //数组添加数组,形成二维数组
}
return arr_1;
}
</script>
</body>
</html>
五.模拟弹幕
模拟B站弹幕效果
要实现功能为,点击发送校验输入框内容,成功校验后生成弹幕,并随机速度向左移动,超出可视区后停止定时器,并且消除该弹幕。
要注意的点是,每一条弹幕都是一个独立的定时器,不能出现点击发送后吧上一条弹幕覆盖的情况,所以不能设置定时器容器 timer 为全局的,而是生成新的弹幕时,容器就要容纳新的定时器。
function getScrolling() {
//1.生成弹幕
let scrolling = document.createElement("p");
scrolling.innerHTML = txt.value;
scrolling.style["height"] = 18 + "px";
//2.设置弹幕随机高度,统一生成在左侧
let rTop = Math.floor(Math.random() * (400 - 18));
console.log(rTop);
scrolling.style["top"] = rTop + "px";
scrolling.style["left"] = 600 + "px";
//3.设置弹幕滚动
let rSpeed = Math.floor(Math.random() * 15);
let timer = null;
timer = setInterval(() => {
let step = 1;
let left = parseInt(scrolling.style["left"]);
scrolling.style["left"] = left - step + "px";
if (left < -scrolling.offsetWidth) {
clearInterval(timer);
screen.removeChild(scrolling);
}
}, rSpeed);
screen.appendChild(scrolling);
}
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./reset.css" />
<style>
body {
background-color: rgba(232, 232, 232, 1);
}
.box {
width: 640px;
height: 490px;
padding: 20px;
background-color: rgba(242, 242, 242, 1);
}
.screen {
width: 600px;
height: 400px;
background-color: rgba(255, 255, 255, 1);
position: relative;
overflow: hidden;
}
.bottom {
width: 600px;
height: 50px;
background-color: skyblue;
display: flex;
}
.bottom input {
width: 496px;
height: 50px;
border: 2px solid skyblue;
padding: 0 10px;
}
.bottom button {
width: 100px;
height: 50px;
background-color: skyblue;
font-size: 20px;
line-height: 50px;
text-align: center;
}
/* 设置弹幕样式 */
.screen p {
display: block;
font-size: 14px;
position: absolute;
top: 400px;
text-align: center;
white-space: nowrap;
}
</style>
</head>
<body>
<div class="box">
<div class="screen"></div>
<div class="bottom">
<input type="text" class="txt" placeholder="输入要发布的弹幕~~" />
<button class="btn">发送</button>
</div>
</div>
<script>
let btn = document.querySelector(".btn");
let txt = document.querySelector(".txt");
let screen = document.querySelector(".screen");
//点击事件监听
btn.addEventListener("click", () => {
if (!txt.value) {
alert("还没有输入喔~ OvO");
return;
}
getScrolling();
});
function getScrolling() {
//1.生成弹幕
let scrolling = document.createElement("p");
scrolling.innerHTML = txt.value;
scrolling.style["height"] = 18 + "px";
//2.设置弹幕随机高度,统一生成在左侧
let rTop = Math.floor(Math.random() * (400 - 18));
console.log(rTop);
scrolling.style["top"] = rTop + "px";
scrolling.style["left"] = 600 + "px";
//3.设置弹幕滚动
let rSpeed = Math.floor(Math.random() * 15);
let timer = null;
timer = setInterval(() => {
let step = 1;
let left = parseInt(scrolling.style["left"]);
scrolling.style["left"] = left - step + "px";
if (left < -scrolling.offsetWidth) {
clearInterval(timer);
screen.removeChild(scrolling);
}
}, rSpeed);
screen.appendChild(scrolling);
}
</script>
</body>
</html>
六.备忘录
实现一个备忘录,能够存入代办事件功能。
、
该案例比较简单,主要难点在于删除和勾选后的数据变化,要和件数统一。
//4.给ul添加事件委托,因为li标签是动态生成的,获取其标签要用事件委托的方式
ul.addEventListener("click", (e) => {
//当点击到删除按钮
if (e.target.classList.contains("delete")) {
ul.removeChild(e.target.parentNode.parentNode); //删除该节点
sum -= 1;
completed ? (completed -= 1) : 0;
uncompleted ? (uncompleted -= 1) : 0;
fn(); //重新调用函数渲染
sum == 0 ? (list_p.className = "") : sum;
}
//5.点击复选框
if (e.target.classList.contains("checkbox")) {
// console.log(e.target.checked);
// if (e.target.checked) {
// completed += 1;
// console.log(completed);
// } else {
// completed -= 1;
// console.log(completed);
// }
//进行数据修改
e.target.checked ? (completed += 1) : (completed -= 1);
!e.target.checked ? (uncompleted += 1) : (uncompleted -= 1);
fn();
}
});
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: rgba(247, 247, 247, 1);
}
.layout {
width: 550px;
background-color: rgba(255, 255, 255, 1);
display: flex;
flex-direction: column;
align-items: center;
border-radius: 10px;
margin: 50px auto;
}
h2 {
height: 40px;
margin: 20px 20px;
}
.list {
width: 420px;
}
.list input {
width: 350px;
height: 40px;
border-radius: 5px;
padding: 10px;
box-sizing: border-box;
}
.list button,
.list-item button {
width: 60px;
height: 40px;
background-color: pink;
color: rgba(255, 255, 255, 1);
outline-style: none;
border: 0;
border-radius: 5px;
}
.list-item button {
width: 40px;
height: 30px;
}
.result div {
margin-top: 10px;
display: flex;
justify-content: space-evenly;
}
.list-item {
width: 420px;
margin-top: 10px;
margin-bottom: 20px;
text-align: center;
}
.list-item ul li {
width: 410px;
display: flex;
/* justify-content: center; */
align-items: center;
justify-content: space-between;
margin: 10px 0;
}
.list-item ul li .left {
display: flex;
}
.list-item ul li p {
margin: 0 20px 0 5px;
}
.list-item > p {
font-size: 12px;
color: gray;
margin-top: 10px;
}
.isShow {
display: none;
}
</style>
</head>
<body>
<div class="layout">
<h2>咸鱼茄子煲の备忘录</h2>
<div class="list">
<input type="text" placeholder="请输入待办" />
<button class="add">添加</button>
<div class="result">
<div>
<span>共0件</span>
<span>已完成 [0]</span>
<span>未完成 [0]</span>
</div>
</div>
</div>
<div class="list-item">
<p>还没有任务哦OvO~~</p>
<ul></ul>
</div>
</div>
<script>
//1.设置三个变量,用以数据更新
let sum = 0;
let completed = 0;
let uncompleted = 0;
//2.获取标签
let inp = document.querySelector(".list input");
let ul = document.querySelector(".list-item ul");
let addBtn = document.querySelector(".add");
let deleteBtn = document.querySelector(".delete");
let result = document.querySelector(".result");
let list_p = document.querySelector(".list-item p");
//3.点击添加按钮实现添加功能
addBtn.addEventListener("click", () => {
let inpValue = inp.value;
if (inpValue == "") return; //判断输入框是否为空
let liHtml = document.createElement("li");
liHtml.innerHTML = `
<div class="left">
<input type="checkbox" class='checkbox'/>
<p>${inpValue}</p>
</div>
<div class="right">
<button class="delete">删除</button>
</div>
`;
ul.insertBefore(liHtml, ul.firstChild); //在顶部插入
sum = ul.children.length; //进行数据更新
uncompleted = ul.children.length;
fn(); //调用渲染函数
list_p.className = "isShow"; //使提示句消失
});
//4.给ul添加事件委托,因为li标签是动态生成的,获取其标签要用事件委托的方式
ul.addEventListener("click", (e) => {
//当点击到删除按钮
if (e.target.classList.contains("delete")) {
ul.removeChild(e.target.parentNode.parentNode); //删除该节点
sum -= 1;
completed ? (completed -= 1) : 0;
uncompleted ? (uncompleted -= 1) : 0;
fn(); //重新调用函数渲染
sum == 0 ? (list_p.className = "") : sum;
}
//5.点击复选框
if (e.target.classList.contains("checkbox")) {
// console.log(e.target.checked);
// if (e.target.checked) {
// completed += 1;
// console.log(completed);
// } else {
// completed -= 1;
// console.log(completed);
// }
//进行数据修改
e.target.checked ? (completed += 1) : (completed -= 1);
!e.target.checked ? (uncompleted += 1) : (uncompleted -= 1);
fn();
}
});
//6.封装渲染函数
function fn(params) {
// console.log(sum, completed, uncompleted);
result.innerHTML = `
<div>
<span>共${sum}件</span>
<span>已完成 [${completed}]</span>
<span>未完成 [${uncompleted}]</span>
</div>
`;
inp.value = "";
}
</script>
</body>
</html>
七.模拟天气查询
和上个案例类似,都是点击获取输入框的value值,与data数据进行匹配,随后将匹配到的数据在页面进行渲染。
代码采用的数组映射map方法,可以看做为增强的for循环,item为数组里的每一个元素,data是数组包对象结构,所以使用两次map方法进行遍历。因为data有四条数据,使用map会触发四次里面的回调函数,此时就需要进行判断结束遍历。
map返回的是经过处理后的数组,通过join方法转成字符串形式,就可以被标签直接使用。
function getData(city) {
let liHtml;
//map类似for循环,会自动遍历,有多少数据就遍历多少次
data.map((item) => {
//如果没有找到输入城市,直接结束当前遍历
if (city !== item.city) return;
//获取到所有的结构
liHtml = item.result
.map((item) => {
return `<li>
<div class="image"><img src="./assets/${item.img}.png" alt="" /></div>
<div class="data">
<div class="date">日期:${item.date}</div>
<div class="day_tem">白天天气:${item.day_tem}°C</div>
<div class="night_tem">夜间天气:${item.night_tem}°C</div>
<div class="week">${item.week}</div>
<div class="wea">${item.wea}</div>
</div>
</li>`;
})
.join(""); //转字符串
});
return liHtml;
}
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: rgba(247, 247, 247, 1);
}
.layout {
width: 550px;
background-color: rgba(255, 255, 255, 1);
display: flex;
flex-direction: column;
align-items: center;
border-radius: 10px;
margin: 50px auto;
}
h2 {
height: 40px;
margin: 20px 20px;
}
.list {
width: 420px;
}
.list input {
width: 350px;
height: 40px;
border-radius: 5px;
padding: 10px;
box-sizing: border-box;
}
.list button,
.list-item button {
width: 60px;
height: 40px;
background-color: pink;
color: rgba(255, 255, 255, 1);
outline-style: none;
border: 0;
border-radius: 5px;
}
.list-item button {
width: 40px;
height: 30px;
}
.result div {
margin-top: 10px;
display: flex;
justify-content: space-around;
}
.list-item {
width: 420px;
margin-top: 10px;
margin-bottom: 20px;
text-align: center;
}
.list-item ul li {
width: 410px;
height: 120px;
display: flex;
/* justify-content: center; */
align-items: center;
justify-content: space-around;
margin: 10px 0;
}
.list-item ul li .left {
display: flex;
}
.list-item ul li p {
margin: 0 20px 0 5px;
}
.list-item ul li img {
width: 100px;
}
.list-item > p {
font-size: 12px;
color: gray;
margin-top: 10px;
}
.isShow {
display: none;
}
</style>
</head>
<body>
<div class="layout">
<h2>咸鱼茄子煲の天气预报</h2>
<div class="list">
<input type="text" placeholder="请输入要查询的城市" />
<button class="btn">查询</button>
</div>
<div class="list-item">
<p>还没有天气哦OvO~~</p>
<ul></ul>
</div>
</div>
<!-- <img src="./assets/cloudy.png" alt="" /> -->
<script src="./weather.js"></script>
<script>
let btn = document.querySelector(".btn");
let inp = document.querySelector(".list input");
let ul = document.querySelector(".list-item ul");
let p = document.querySelector(".list-item p");
btn.addEventListener("click", () => {
if (!inp.value) {
alert("还没有输入喔~~");
return;
}
// console.log(inp.value);
let city = inp.value;
// console.log(getData(city));
let liHtml = getData(city);
if (!liHtml) {
p.style["display"] = "block";
ul.innerHTML = "";
return;
}
p.style["display"] = "none";
ul.innerHTML = liHtml;
});
function getData(city) {
let liHtml;
//map类似for循环,会自动遍历,有多少数据就遍历多少次
data.map((item) => {
//如果没有找到输入城市,直接结束当前遍历
if (city !== item.city) return;
//获取到所有的结构
liHtml = item.result
.map((item) => {
return `<li>
<div class="image"><img src="./assets/${item.img}.png" alt="" /></div>
<div class="data">
<div class="date">日期:${item.date}</div>
<div class="day_tem">白天天气:${item.day_tem}°C</div>
<div class="night_tem">夜间天气:${item.night_tem}°C</div>
<div class="week">${item.week}</div>
<div class="wea">${item.wea}</div>
</div>
</li>`;
})
.join(""); //转字符串
});
return liHtml;
}
</script>
</body>
</html>
八.模拟放大镜功能
左侧为一倍图,右侧为二倍图或其他比例大图,鼠标移入后,左侧遮罩层跟随鼠标移动,右侧大图也做出偏移,模拟放大效果。
要做到理想效果,最重要的是弄清楚里面各种各样的距离关系。
首先是要得知鼠标在小图中的位置,以便引导遮罩层移动,e.pageX 是鼠标距离可视区的横向距离,减去盒子距离可视区的横向距离small.offsetLeft,就可以得到鼠标位置,但此时遮罩层是左上角跟随移动,我们需要的是遮罩层中心跟随移动。
let x = e.pageX - small.offsetLeft;
let y = e.pageY - small.offsetTop;
再减去遮罩层的一半宽高,就可以让遮罩层中心跟随移动了。
let leftX = x - mask.offsetWidth / 2;
let topY = y - mask.offsetHeight / 2;
small.addEventListener("mousemove", (e) => {
//获取鼠标在盒子内的坐标
// console.log(e.pageX - small.offsetLeft, e.pageY - small.offsetTop);
let x = e.pageX - small.offsetLeft;
let y = e.pageY - small.offsetTop;
let leftX = x - mask.offsetWidth / 2;
let topY = y - mask.offsetHeight / 2;
// console.log(leftX, topY);
});
接着就需要做出限制,使遮罩层只能在盒子内部进行偏移。
//判断超出位置的限制
if (leftX <= 0) {
leftX = 0;
} else if (leftX >= small.offsetWidth - mask.offsetWidth) {
leftX = small.offsetWidth - mask.offsetWidth;
}
if (topY <= 0) {
topY = 0;
} else if (topY >= small.offsetHeight - mask.offsetHeight) {
topY = small.offsetHeight - mask.offsetHeight;
}
mask.style["left"] = leftX + "px";
mask.style["top"] = topY + "px";
最后就需要计算大图的偏移距离,这里采用的方案是,计算遮罩层移动距离的比例,大图也做相应的偏移比例。也就是说,遮罩层动百分之几,大图也移动百分之几。
//大图偏移
//遮罩层偏移距离/遮罩层最大偏移距离 * 大盒子最大偏移距离
//比如说,遮罩层移动到一半,就是 0.5*(800-540) = 130
//遮罩层移动到顶,就是 1*(800-560) =260
let big_leftX = big_img.offsetWidth - big.offsetWidth;
let big_topY = big_img.offsetHeight - big.offsetHeight;
let big_img_x =
(leftX / (small.offsetWidth - mask.offsetWidth)) * big_leftX;
let big_img_y =
(topY / (small.offsetHeight - mask.offsetHeight)) * big_topY;
big_img.style["left"] = -big_img_x + "px";
big_img.style["top"] = -big_img_y + "px";
console.log(-big_img_x, -big_img_y);
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.banner {
display: flex;
width: 1000px;
margin: 0 auto;
}
.small {
width: 350px;
height: 350px;
background: url(./images/small.jpg) no-repeat;
position: relative;
}
.mask {
width: 240px;
height: 240px;
background-color: rgba(200, 196, 196, 0.6);
position: absolute;
top: 0;
left: 0;
}
.big {
width: 540px;
height: 540px;
/* background: url(./images/big.jpg); */
margin-left: 20px;
overflow: hidden;
position: relative;
}
.big img {
position: absolute;
top: 0;
left: 0;
}
.active {
display: none;
}
</style>
</head>
<body>
<div class="banner">
<div class="small">
<div class="mask"></div>
</div>
<div class="big">
<img src="./images/big.jpg" alt="" />
</div>
</div>
<script>
let small = document.querySelector(".small");
let mask = document.querySelector(".mask");
let big = document.querySelector(".big");
let big_img = document.querySelector(".big img");
small.addEventListener("mousemove", (e) => {
//获取鼠标在盒子内的坐标
// console.log(e.pageX - small.offsetLeft, e.pageY - small.offsetTop);
let x = e.pageX - small.offsetLeft;
let y = e.pageY - small.offsetTop;
let leftX = x - mask.offsetWidth / 2;
let topY = y - mask.offsetHeight / 2;
// console.log(leftX, topY);
//判断超出位置的限制
if (leftX <= 0) {
leftX = 0;
} else if (leftX >= small.offsetWidth - mask.offsetWidth) {
leftX = small.offsetWidth - mask.offsetWidth;
}
if (topY <= 0) {
topY = 0;
} else if (topY >= small.offsetHeight - mask.offsetHeight) {
topY = small.offsetHeight - mask.offsetHeight;
}
mask.style["left"] = leftX + "px";
mask.style["top"] = topY + "px";
//大图偏移
// console.log(big_img.offsetWidth / small.offsetWidth);
//大图/小图 * 小图偏移距离
//遮罩层偏移距离/遮罩层最大偏移距离 * 大盒子最大偏移距离
//比如说,遮罩层移动到一半,就是 0.5*(800-540) = 130
//遮罩层移动到顶,就是 1*(800-560) =260
let big_leftX = big_img.offsetWidth - big.offsetWidth;
let big_topY = big_img.offsetHeight - big.offsetHeight;
let big_img_x =
(leftX / (small.offsetWidth - mask.offsetWidth)) * big_leftX;
let big_img_y =
(topY / (small.offsetHeight - mask.offsetHeight)) * big_topY;
big_img.style["left"] = -big_img_x + "px";
big_img.style["top"] = -big_img_y + "px";
console.log(-big_img_x, -big_img_y);
});
</script>
</body>
</html>
总结时间
八个案例写下来,不说很有难度吧,确实也不简单,细节问题很多,尤其是对于定时器方面的操作,还是很考验细心程度的。
最后的感受是,DOM操作挺好玩的 QAQ 。。。
再接再厉!
学前端开发,当赛博黑奴。。。