前端-JS学习笔记-华为商城

https://www.vmall.com/index.html

1. 代码

vmall.zip

2. 创建元素方法

2.1. 通过createElement()

  • 需要搭配append()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 产品展示元素
var productEl = document.querySelector(".product");
// 产品展示数据来源
var showList = [].concat(resultList);


function createEle1() {
productEl.innerHTML = "";
for (var i = 0; i < showList.length; i++) {
const result = showList[i];

// 创建元素
var item = document.createElement("div");
item.className = "item";
productEl.append(item);

// a链接
var a = document.createElement("a");
a.href = "#";
item.append(a);

// 元素中的图片
var img = document.createElement("img");
img.className = "album";
img.src = baseUrl + result.photoPath +"800_800_"+ result.photoName;
a.append(img);

// 标题
var title = document.createElement("p");
title.className = "title oneline";
title.innerText = result.name;
a.append(title);

// 折扣
var discount = document.createElement("p");
discount.className = "discount online";
discount.innerText = result.promotionInfo;
a.append(discount);

// 价格
var price = document.createElement("p");
price.className = "price";
price.innerHTML = "&yen;"+result.price;
a.append(price);

// 福利
var welfare = document.createElement("div");
welfare.className = "welfare";
var services = result.promoLabels;
for (const service of services) {
var span = document.createElement("span");
span.innerText = service;
welfare.append(span);
}
a.append(welfare);

// 评论
var comment = document.createElement("div");
comment.className = "comment";
var span = document.createElement("span");
span.innerText = result.rateCount + "人评论";
comment.append(span);
span = document.createElement("span");
span.innerText = result.goodRate + "%好评";
comment.append(span);
a.append(comment);
}
addEmptySpace(4);
}

2.2. 使用innerHTML

  • 将所有子元素内容拼接好后通过innerHTML渲染到网页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function createEle2() {
productEl.innerHTML = "";
var str = "";
for (var i = 0; i < showList.length; i++) {
const result = showList[i];

// 图片地址
imgSrc = baseUrl + result.photoPath +"800_800_"+ result.photoName;
// 福利
var services = result.promoLabels;
var welfare = "";
for (const service of services) {
welfare += `<span>${service}</span>`;
}

// 拼接元素
str += `
<div class="item">
<a href="#">
<img src="${imgSrc}" class="album">
<p class="title oneline">${result.name}</p>
<p class="discount">${result.promotionInfo}</p>
<p class="price">&yen;${result.price}</p>
<div class="welfare">${welfare}</div>
<div class="comment">
<span>${result.rateCount}人评论</span>
<span>${result.goodRate}%好评</span>
</div>
</a>
</div>`;
}
productEl.innerHTML = str;
addEmptySpace(4);
}

3. 过滤实现

  • resultList数组中存放的一直是原始数据

  • 通过数组的filter方法筛选数据

    • 匿名函数返回true时,数据会被保留
    • typeFlag - 分类栏的数据保留标志,serviceFlag - 服务优惠栏的数据保留标志,只有当两者都为true时,才能确保数据是符合过滤标准的
  • 分类栏的过滤

    • 当 typeFilters数组为空时,for循环不会执行,所有原始数据通过筛选
    • 当 typeFilters数组存入数据,如 [“笔记本”]时,判断resultList数据每一项的skuName中是否包含”笔记本”字符串。若没有,则typeFlag为false,立即退出循环,也可以退出这次筛选,因为typeFlag已经为false,return的一定是false
  • 服务优惠栏的过滤

    • 当 serviceFilters数组为空时,for循环也不会执行,所有原始数据通过筛选
    • 当 serviceFilters数组存入数据,如 [“仅看有货”]时,判断resultList数据每一项的services数组中是否包含”仅看有货”元素。若没有,则serviceFlag为false,立即退出循环
  • 最终需要重新根据showList生成内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 分类栏的过滤
var typeFilters = [];
// 服务优惠栏的过滤
var serviceFilters = [];

function filterArr() {
showList = resultList.filter(function(item){
var typeFlag = true;
var serviceFlag = true;
// 分类栏筛选
for (var arr of typeFilters) {
if (item.skuName.indexOf(arr) === -1) {
typeFlag = false;
break;
}
}
if(typeFlag) {
// 服务优惠栏筛选
for (var arr of serviceFilters) {
if(!item.services.includes(arr)) {
serviceFlag = false;
break;
}
}
}
return typeFlag&&serviceFlag;
})
// 重新生成内容
createEle2();
}

4. 过滤的选择

  • 用于typeFilters,serviceFilters数组的生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function clickChoose(event,filters) {
// 点击项
var item = event.target;
// 点击项内容
var value = item.innerHTML;
// 排除栏目标题
if(item === this.children[0]) return;
// 判断是否处于激活状态
if(item.classList.contains("active")) {
// 找到内容在过滤数组中的索引值
var idx = filters.indexOf(value);
// 删除该元素
filters.splice(idx,1);
// 点击项变回未激活状态
item.classList.remove("active");
}
else {
// 过滤数组中添加内容
filters.push(value);
// 点击项处于激活状态
item.classList.add("active");
}
// 根据现在的过滤数组内容,重新生成showList数组
filterArr();
}
  • 分类栏点击
1
2
3
4
5
var typeEl = operationEl.querySelector(".type");

typeEl.onclick = function(event) {
clickChoose.apply(this,[event,typeFilters]);
}
  • 服务优惠栏点击
1
2
3
4
5
var services = operationEl.querySelector(".services");

services.onclick = function(event) {
clickChoose.apply(this,[event,serviceFilters]);
}

5. 排序实现

5.1. 排序栏的数据准备

  • data-key属性对应原始数据中的key值
  • JavaScript中通过 element.dataset.key可以拿到
1
2
3
4
5
6
7
<ul class="order">
<li>排序:</li>
<li data-key="default" class="active">综合</li>
<li data-key="goodRate">好评率</li>
<li data-key="rateCount">评论数</li>
<li data-key="price">价格</li>
</ul>

5.2. 排序栏的点击事件

  • 综合对应default,它并不用排序,只是对当前两个过滤数组进行再一次的过滤
  • 其他的通过数组的sort函数进行排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 排序栏
var orderEl = operationEl.querySelector(".order");
// 记录当前激活的项
var orderActiveEl = orderEl.children[1];

orderEl.onclick = function(event) {
// 点击项
var item = event.target;
// 排除栏目标题
if(item === this.children[0]) return;
// 删除原有激活项的样式
orderActiveEl.classList.remove("active");
// 点击项激活样式
item.classList.add("active");
// 记录点击项为当前激活项
orderActiveEl = item;
// 拿到点击项的data-key值
var key = item.dataset.key;
// 如果是综合项,用原始数据过滤
if(key === "default") {
filterArr();
}
else {
// 用数组sort函数进行排序
showList = showList.sort(function(item1,item2) {
return item2[key] - item1[key];
});
// 重新生成product中的展示内容
createEle2();
}
}

6. 图片轮播-透明度变化

6.1. 绝对定位

  • 所有图都绝对定位叠放在同一个位置,只有第一张图透明度为1
  • 设置的transition动画可使每个图片展示有透明度变化效果
1
2
3
4
5
6
7
8
9
10
.banner-opacity .item {
position: absolute;
left: 0;
top: 0;
opacity: 0;
transition: opacity 500ms linear;
}
.banner-opacity .item:first-child {
opacity: 1;
}

6.2. 移动实现

  • prevoiusIndex 记录当前所在位置
  • currentIndex 记录将要移动到的位置
  • len 记录原始数据的数组长度
1
2
3
4
var prevoiusIndex = 0;
var currentIndex = 0;

var len = banners.length;
  • 判断边界
    • 当前是最后一张图(prevoiusIndex=len-1)时,后移下一张图应该是第一张(currentIndex=0)
    • 当前是第一张图(prevoiusIndex=0)时,前移下一张图应该是最后一张(currentIndex=len-1)
  • 改变索引组的激活项
    • 当前项取消激活
    • 目标项激活
  • 改变图片透明度
    • 当前图透明度改为0
    • 目标项透明度改为1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var bannerOpacity = document.querySelector(".banner-opacity");
// 底部索引
var indicatorList = bannerOpacity.querySelector(".indicator");
// 图片组
var bannerList = bannerOpacity.querySelector(".list");

function move() {
// 边界判断
if (currentIndex === len) {
currentIndex = 0;
}
if(currentIndex === -1 ) {
currentIndex = len - 1;
}
// 索引
indicatorList.children[prevoiusIndex].classList.remove("active");
indicatorList.children[currentIndex].classList.add("active");

// 图片
bannerList.children[prevoiusIndex].style.opacity = 0;
bannerList.children[currentIndex].style.opacity = 1;
}

6.3. 移动变化

6.3.1. 左右按钮的点击

  • 按钮改变的就是 prevoiusIndex 和 currentIndex值
1
2
3
4
5
6
7
8
9
10
11
12
13
var leftBtn = bannerOpacity.querySelector(".left");
var rightBtn = bannerOpacity.querySelector(".right");

leftBtn.onclick = function() {
prevoiusIndex = currentIndex;
currentIndex--;
move();
}
rightBtn.onclick = function() {
prevoiusIndex = currentIndex;
currentIndex++;
move();
}

6.3.2. 索引组的点击

  • 索引组的点击函数放在了元素创建中,是为了放到每个项的对应索引值 li.index = i
  • 每个项点击改变的也是 prevoiusIndex 和 currentIndex值
1
2
3
4
5
6
7
8
9
10
11
12
13
// 索引
var li = document.createElement("li");
if(i===0) {
li.className = "active";
}
li.index = i;
indicatorList.append(li);
// 索引按钮
li.onclick = function() {
prevoiusIndex = currentIndex;
currentIndex = this.index;
move();
}

6.4. 自动轮播

  • 设置一个定时器,每3s运行一次
  • 定时器内部跟右按钮的点击一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var timer = null;

function startTime() {
if (timer) return;
timer = setInterval(function(){
prevoiusIndex = currentIndex;
currentIndex++;
move();
},
3000);
}
function endTime() {
if (!timer) return;
clearInterval(timer);
timer = null;
}

6.5. 鼠标与定时器

  • 鼠标在整个 div.banner元素中时,定时器关闭
  • 鼠标离开时,定时器重启
1
2
3
4
5
6
7
8
bannerOpacity.onmouseenter = function() {
// console.log("enter");
endTime();
}
bannerOpacity.onmouseleave = function() {
// console.log("leave");
startTime();
}

6.6. 页面可见与定时器

  • 当页面不可见时,定时器关闭
  • 页面可见时,定时器重启
1
2
3
4
5
6
7
8
document.onvisibilitychange = function() {
if (document.visibilityState === "visible") {
startTime();
}
if (document.visibilityState === "hidden") {
endTime();
}
}

7. 图片轮播-整个ul移动

7.1. flex布局

  • 所有图片水平排布,通过 整个ul.list的 translateX 的改变来达到移动效果
1
2
3
4
5
6
7
.banner-translatex .list{
display: flex;
/* transform: translateX(-100%); */
}
.banner-translatex .item {
width: 100%;
}

7.2. 图片克隆

  • 第一张图片左移时,为了实现移动的效果,复制最后一张图片放在其左边
  • 最后一张图片右移时,为了实现移动的效果,复制第一张图片放在其右边
  • 这样导致视口中实现的是最后一张图片,所以需要将 ul.list 向左移动 -100%,显示第一张图
1
2
3
4
5
6
7
8
9
10
11
var bannerTranslateX = document.querySelector(".banner-translatex");

function cloneImg() {
var first = bannerListTranslateX.children[0].cloneNode(true);
var last = bannerListTranslateX.children[length-1].cloneNode(true);
// 后插
bannerListTranslateX.append(first);
// 前插
bannerListTranslateX.prepend(last);
bannerListTranslateX.style.transform = "translateX(-100%)";
}

7.3. 移动实现

  • length - 记录原始数据的数组长度
  • preIndex 记录当前所在位置,理想中没有克隆的第一张,最后一张的位置
  • curIndex 记录将要移动到的位置,同样是理想中的位置
1
2
3
var length = banners.length;
var preIndex = 0;
var curIndex = 0;
  • 当达到理想中的最后一张时,因为其后面还有第一张图片副本,所以先进行动画左移,再进行边界判断,无动画的移动至第一张图片位置
  • 当达到理想中的第一张时,因为其前面还有最后一张图片副本,所以先进行动画左移,再进行边界判断,无动画的移动至最一张图片位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function moveTranslateX() {
bannerListTranslateX.style.transform = `translateX(-${(curIndex+1)*100}%)`;
bannerListTranslateX.style.transition = `transform ${transformTime}ms linear`;
// 边界判断的
if (curIndex === length) {
moveTranslateXTimer(1);
curIndex = 0;
}
if(curIndex === -1 ) {
moveTranslateXTimer(len);
curIndex = length - 1;
}
// 索引
indicatorListTranslateX.children[preIndex].classList.remove("active");
indicatorListTranslateX.children[curIndex].classList.add("active");
}

var transformTime = 500;
// 动画结束后,无动画的移动至指定位置
function moveTranslateXTimer(num) {
setTimeout(function() {
bannerListTranslateX.style.transform = `translateX(-${num*100}%)`;
bannerListTranslateX.style.transition = "none";
},transformTime);
}

7.4. 移动变化

7.4.1. 左右按钮的点击

1
2
3
4
5
6
7
8
9
10
11
12
13
var leftTranslatexBtn = bannerTranslateX.querySelector(".left");
var rightTranslatexBtn = bannerTranslateX.querySelector(".right");

leftTranslatexBtn.onclick = function() {
preIndex = curIndex;
curIndex--;
moveTranslateX();
}
rightTranslatexBtn.onclick = function() {
preIndex = curIndex;
curIndex++;
moveTranslateX();
}

7.4.2. 索引组的点击

1
2
3
4
5
6
7
8
9
10
11
12
13
// 索引
var li = document.createElement("li");
if(i===0) {
li.className = "active";
}
li.index = i;
indicatorListTranslateX.append(li);
// 索引按钮
li.onclick = function() {
preIndex = curIndex;
curIndex = this.index;
moveTranslateX();
}

7.5. 自动轮播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var timertranslatex = null;

function startTimeTranslateX() {
if (timertranslatex) return;
timertranslatex = setInterval(function(){
preIndex = curIndex;
curIndex++;
moveTranslateX();
},
3000);
}
function endTimeTranslateX() {
if (!timertranslatex) return;
clearInterval(timertranslatex);
timertranslatex = null;
}

7.6. 鼠标与定时器

1
2
3
4
5
6
7
8
9
startTimeTranslateX();
// 移入
bannerTranslateX.onmouseenter = function() {
endTimeTranslateX();
}
// 移出
bannerTranslateX.onmouseleave = function() {
startTimeTranslateX();
}
本文结束  感谢您的阅读