以前是一直没用过这种思想进行过开发工作的,都是面向过程,把某一个部分完成了就行,但是越写到后边接触的越多我就会发现,有很多过程是重复的,这就是解决这个问题的共性,如果我把这些共性抽象出来,那想必就省事多了,就比如你用原生js做一个多层级的tab切换栏,你真不会一层一层地去写吧,事件也是一层一层的加,当然咯,一层层这样写下去也不是不可以,只要你时间够,但是万一以后要加功能呢?你还来得及吗,或者说你对你写的这个过程你还了解吗?
<div class="digital-program">
<div class="digital-program-container">
<h2>中小企业数据电子化解决方案</h2>
<div class="tab tab1">
<ul class="tab-list">
<li class="tab-item">
<div class="tab-tilte">
<h3>Epson Smart Panel</h3>
<h4>(无线应用解决方案)</h4>
</div>
<div class="tab-content">11111</div>
</li>
<li class="tab-item">
<div class="tab-tilte">
<h3>Epson Smart Panel</h3>
<h4>(无线应用解决方案)</h4>
</div>
<div class="tab-content">
<div class="menu">
<ul class="menu-list">
<li class="menu-item">
<div class="menu-item-title" collapsed="false">
<i class="iconfont download"></i>
<span>软件下载</span>
</div>
<div class="menu-item-content">
<div class="download-info">
<h5>适用手机系统</h5>
<ul class="download-info-class-list">
<li class="download-info-class-item">
<span
>iOS系统和设备 : iOS 9.0或以上版本的iPhone/iPod
touch/iPad</span
>
</li>
<li class="download-info-class-item">
<span
>Android系统和设备 : Android
4.4或以上版本的设备</span
>
</li>
<li class="download-info-class-item">
<span>适用机型:</span>
<div class="tab">
<ul class="tab-list">
<li class="tab-item">
<div class="tab-tilte">
<span>DS-360W</span>
</div>
<div class="tab-content">
<div class="qrcode-box">
<div class="qrcode">
<div class="qrcode-img-box">
<img
src="./img/bg-img/qrcode.png"
/>
</div>
<div class="qrcode-intro">
<i class="iconfont iphone"></i>
<span>扫描二维码</span>
<span>下载IOS客户端</span>
</div>
</div>
<div class="qrcode">
<div class="qrcode-img-box">
<img
src="./img/bg-img/qrcode.png"
/>
</div>
<div class="qrcode-intro">
<i class="iconfont android"></i>
<span>扫描二维码</span>
<span>下载IOS客户端</span>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
</li>
<li class="menu-item">
<div class="menu-item-title" collapsed="false">
<i class="iconfont install"></i>
<span>安装指南</span>
</div>
<div class="menu-item-content">33333</div>
</li>
<li class="menu-item">
<div class="menu-item-title" collapsed="false">
<i class="iconfont use-guide"></i>
<span>使用指南</span>
</div>
<div class="menu-item-content">22222</div>
</li>
<li class="menu-item">
<div class="menu-item-title" collapsed="true">
<i class="iconfont question"></i>
<span>常见问题</span>
</div>
<div class="collapse-list" style="visibility: hidden">
<div class="collapse-item">
<span>IOS版FAQ</span>
</div>
<div class="collapse-item">
<span>Android版FAQ</span>
</div>
</div>
<div class="menu-item-content">33333</div>
</li>
</ul>
<div class="menu-content-container"></div>
</div>
</div>
</li>
<li class="tab-item">
<div class="tab-tilte">
<h3>Epson DS-730N</h3>
<h4>(网络LAN应用解决方案)</h4>
</div>
<div class="tab-content">33333</div>
</li>
</ul>
<div class="tab-content-container"></div>
</div>
</div>
</div>
上面是html架构, 总的说一下,我要完成下面的效果:
没错,就是一个三级联动,因为现在活还不算太多,所以我想好好趁着这段时间学些底层的东西稳固下基础,所有的任务我都用原生js进行开发,顺便了解下各个常用组件的运行机制。 废话不多说,先分析下这个组件
function totransFormArr(fakeArr) {
let newArr = [];
fakeArr.forEach((element, index) => {
newArr[index] = fakeArr[index];
});
return newArr;
}
// tab栏模块
var tabDomList = totransFormArr(document.querySelectorAll(" .tab1 .tab-item"));
var tabContentDomList = totransFormArr(
document.querySelectorAll(".tab1 .tab-content")
);
// 当前指示tab的index的值
var tabIndex = 1;
function initTable1() {
let tabContainer = document.querySelector(".tab1 .tab-content-container");
tabContainer.innerHTML = tabContentDomList[tabIndex].innerHTML;
tabDomList[tabIndex].querySelector(".tab-tilte").style.color = "#0c89ef";
tabDomList.forEach((e, index) => {
e.removeChild(tabContentDomList[index]);
e.querySelector(".tab-tilte").addEventListener("click", () => {
let tabContainer = document.querySelector(".tab1 .tab-content-container");
let titleDom = e.querySelector(".tab-tilte");
titleDom.style.color = "#0c89ef";
tabContainer.innerHTML = tabContentDomList[index].innerHTML;
tabDomList[tabIndex].querySelector(".tab-tilte").style.color = "#000";
tabIndex = index;
});
});
}
function initTable2() {
let tabContainer = document.querySelector(".tab1 .tab-content-container");
tabContainer.innerHTML = tabContentDomList[tabIndex].innerHTML;
tabDomList[tabIndex].querySelector(".tab-tilte").style.color = "#0c89ef";
tabDomList.forEach((e, index) => {
e.removeChild(tabContentDomList[index]);
e.querySelector(".tab-tilte").addEventListener("click", () => {
let tabContainer = document.querySelector(".tab1 .tab-content-container");
let titleDom = e.querySelector(".tab-tilte");
titleDom.style.color = "#0c89ef";
tabContainer.innerHTML = tabContentDomList[index].innerHTML;
tabDomList[tabIndex].querySelector(".tab-tilte").style.color = "#000";
tabIndex = index;
});
});
}
initTable1();
// 获取tab类下的ul li的dom
// 得到一个li的dom的数组之后 可以设定一个indexLi 表示当前指示的li的下标
// 然后在依次得到与li并列的 tab-content 依次放置在一个数组当中 并从原dom结构中移除
// 再根据指示的indexLi 在ul标签下返回一个container tab-content-container 这个容器当中包含了之前获取到的tab-content的内容
上边是俩个tab的初始化函数; 我一开始的想法就是先把各个标题对应的内容都写在一块,然后在初始化的时候把内容都抽离出来,对就是removeChild(...) 把要切换的内容都抽离出来存在一个全局变量当中,切换到它的时候再取处理来。这种办法是我首先想到的,其实只是做单个tab切换栏是没有问题的,但当我们在里边嵌套其他的切换栏的时候就会出现样式变了,js做的一些处理也没了。我想这的原因可能是我把原状态的dom存起来了,但是后边我又对这些dom进行了一些操作,然后状态就没更新上,所以样式和js效果都不对劲,后来我想固定住这个状态,下一次dom刷新的时候我就再存一次,结果还是不可以,这里我倒是想了很久,或许是更换位置后dom之前的操作没保存下来吧。
如图所示,一开始是好的,但是当你切换的时候就变成乱码了。
// 菜单部分
// 默认选中菜单index
var menuSelectedIndex = 0;
// 获取所有的menu-item
var menuItemList = document.querySelectorAll(".menu-list .menu-item");
// 菜单内容列表
var menuContentList = [];
// 初始化menu
function initMenu() {
let contentBox = document.querySelector(".menu-content-container");
// 首先是初始化默认选项
// 得到默认选项的content
var menuDeafult = menuItemList[0].querySelector(".menu-item-content");
menuItemList[0].querySelector(
".menu-item-title"
).parentNode.style.backgroundColor = "#0c89ef";
menuItemList[0].querySelector(".menu-item-title").parentNode.style.color =
"#fff";
contentBox.innerHTML = menuDeafult.innerHTML;
// contentBox.innerHTML = menuDeafult.innerHTML;
// 给menuitem初始化 该隐藏的都隐藏掉
menuItemList.forEach((e, index) => {
let menuTitle = e.querySelector(".menu-item-title");
let menuContent = e.removeChild(e.querySelector(".menu-item-content"));
menuContentList.push(menuContent);
console.log(menuContent);
menuTitle.addEventListener("click", () => {
menuTitle.parentNode.style.backgroundColor = "#0c89ef";
menuTitle.parentNode.style.color = "#fff";
let lastSelected = menuItemList[menuSelectedIndex];
lastSelected.style.backgroundColor = "#fff";
lastSelected.style.color = "#000";
// 在相应位置显示
contentBox.innerHTML = menuContentList[index].innerHTML;
menuSelectedIndex = index;
});
});
}
initMenu();
然后我还重新定义了一个menu初始化事件,加上去之后整个三级联动就特别混乱了。再加之我要做的是响应式,在手机上也要适配,也要用,所以,最后我放弃了这种面向过程的写法。
面向对象来袭
这时我就想换个思路了,既然之前是状态没保存上,那我怎么样能随时利用这个状态呢。我想了一个晚上也没有啥思路,第二天一到公司,突然就灵光一现,对,面向对象,就是面向对象,一个tab是一个对象,tab栏里有标题,内容,当前切换的index等等,把所有属性都融入到这个tab栏里,用什么就从自身对象拿什么,不用担心状态乱套。 说干就干,我写了俩个对象,一个是tab对象,另一个是Collapse对象。
// 定义一个切换工具的类
function SwitchTabTool(
initClass,
direction,
tabItemTitleNomalStyle,
tabItemTitleSelectedStyle,
isShowSelectedBar,
defaultIndex,
beforeIcon,
afterIcon
) {
this.initClass = initClass;
this.direction = direction; // 方向
this.tabItemTitleNomalStyle = tabItemTitleNomalStyle; // 正常状态下的tabItem样式
this.tabItemTitleSelectedStyle = tabItemTitleSelectedStyle; // 选中状态下的tabItem样式
this.isShowSelectedBar = isShowSelectedBar; // 是否显示选中指示条 如果显示的话就在顶层元素添加一个tab-item-stop-sign类
this.selectedIndex = defaultIndex; // 默认状态下选中的item项
this.beforeIcon = beforeIcon || ""; // 标题前图标
this.afterIcon = afterIcon || ""; // 标题后图标
this.Switchdom; // 存放switch的dom
this.initSwitchTab();
}
// 这是折叠面板的对象
function Collapse() {
let arrtributes = { ...arguments[0] };
this.initClass = arrtributes.initClass; // 初始化类名
this.beforeIconList;
this.afterIconList;
this.initCollapse();
}
// tab初始化方法
SwitchTabTool.prototype.initSwitchTab = function () {
// 1 获取switchTool
let s = document.querySelector("." + this.initClass);
this.Switchdom = s;
// 2 确定方向
if (this.direction === "column") {
document
.querySelector("." + this.initClass + " .switchTabContanier")
.classList.add("columnRange");
} else {
document
.querySelector("." + this.initClass + " .switchTabContanier")
.classList.add("rowRange");
}
// 3 确定是否显示指示条
if (this.isShowSelectedBar) {
s.classList.add("ShowSelectedBar");
}
// 4 获取完成之后是对title部分的处理 首先获取switchTabItem 注意是直接子元素 以免后面有嵌套的同类工具
let switchTabItemList = document.querySelectorAll(
"." + this.initClass + ">" + ".switchTabContanier>.switchTab-item"
);
// 5 然后是循环 根据对应的item下面的title以及参数对各项功能进行配置
switchTabItemList.forEach((e, index) => {
let titleDom = e.querySelector(".switchTab-item-title");
// 1、前后加上对应icon
if (this.beforeIcon.length) {
let appendEle = document.createElement("i");
appendEle.setAttribute("class", "iconfont " + this.beforeIcon);
titleDom.insertBefore(appendEle, titleDom.querySelector(".title"));
}
if (this.afterIcon.length) {
let appendEle = document.createElement("i");
appendEle.setAttribute("class", "iconfont " + this.afterIcon);
titleDom.appendChild(appendEle);
}
// 2、添加默认状态样式
titleDom.classList.add(this.tabItemTitleNomalStyle);
// 3、添加默认选中状态的样式
if (index === this.selectedIndex) {
titleDom.classList.add(this.tabItemTitleSelectedStyle);
e.querySelector(".content").style.display = "flex";
}
// 4、给每个title加上点击事件
titleDom.addEventListener("click", (event) => {
if (index !== this.selectedIndex) {
titleDom.classList.add(this.tabItemTitleSelectedStyle);
titleDom.parentNode.querySelector(".content").style.display =
"flex";
switchTabItemList[this.selectedIndex]
.querySelector(".switchTab-item-title")
.classList.remove(this.tabItemTitleSelectedStyle);
switchTabItemList[this.selectedIndex].querySelector(
".content"
).style.display = "none";
this.selectedIndex = index;
}
event.stopPropagation();
});
});
};
// 折叠面板初始化方法
Collapse.prototype.initCollapse = function () {
// 获取Collapse的dom
let collapseDom = document.querySelector("." + this.initClass);
// 获取此Collapse下面所有的直接collapse-item子元素
let collapseItemList = document.querySelectorAll(
"." + this.initClass + ">" + ".collapse-item"
);
// 上次展开的index
let spanIndex = -1;
// 循环collapseItemList 获取title 获取并存放beforeIcon 和 AfterIcon
collapseItemList.forEach((e, index) => {
e.querySelector("div").classList.add("collapse-item-content");
// 获取title beforeIcon AfterIcon isCollapsed(默认为关闭状态)
let title = e.getAttribute("title") || "";
let beforeIcon = e.getAttribute("beforeIcon") || "verticalLine";
let afterIcon = e.getAttribute("afterIcon") || "down";
let isCollapsed = e.getAttribute("isCollapsed") || true;
let appendHtml = document.createElement("div");
appendHtml.innerHTML = `
<div class='collapse-item-title-front'>
<i class='iconfont ${beforeIcon}'></i>
<span class='collapse-item-title'>${title}</span>
</div>
<i class='iconfont ${afterIcon}'></i>
`;
appendHtml.classList.add("collapse-item-tag");
e.insertBefore(appendHtml, e.querySelector(".collapse-item-content"));
// 添加点击事件
e.querySelector(".collapse-item-tag").addEventListener("click", () => {
// 判断是否是折叠的
if (isCollapsed) {
// 内容出现
e.querySelector(".collapse-item-content").style.display = "flex";
// 更换图标
e.querySelector(".collapse-item-tag>.iconfont").classList.replace(
`${afterIcon}`,
afterIcon === "add" ? "reduce" : "up"
);
// 添加展开时候的样式 span-style
e.querySelector(
".collapse-item-title-front .collapse-item-title"
).classList.add("span-style");
// 更换折叠状态
isCollapsed = false;
spanIndex = index;
} else {
// 折叠起来 内容收起
e.querySelector(".collapse-item-content").style.display = "none";
// 更换图标
e.querySelector(".collapse-item-tag>.iconfont").classList.replace(
afterIcon === "add" ? "reduce" : "up",
`${afterIcon}`
);
// 给现在这个项移除展开样式
e.querySelector(
".collapse-item-title-front .collapse-item-title"
).classList.remove("span-style");
isCollapsed = true;
}
event.stopPropagation();
});
});
};
经过上述我的咔咔一顿操作,总算是可以了。下图是效果:
上边是web端的
这个是移动端的
看着就舒服哈哈哈。 但是还是得总结下我这开发过程存在的不足之处:
1、对象属性抽象的不够精确,就像第二个collapse对象,完全就弄错了,collapse能提供什么呢,emm,提供初始化的顶层dom元素变量?添加宽高属性,依照我使用Elementui与swiper的经验,我猜想应该是提供一个整体的环境,虽说看起来是没多大用,但是你想要进行什么操作都必须带上这个最顶级的dom元素变量进行操作。然后就是第二层我应该用collapse-item继承collapse的。上边我逻辑错了,一股脑全加载collapse上边了。
2、就是样式的隔离,这个是个特别大的问题,在开发的过程中我感觉我重复写了特别多的样式,不是这个样式被那个样式覆盖了,就是那个被这个替代了,css样式写得毫无结构化可以言,只能说是缝缝补补的那种。对此我也想到了一个方法,就是抽象出公用属性,自定义属性先不写,用到的时候再写,不能使用后代选择器,否则一定会被覆盖,最好再加上自定义的类名。
3、对于整个组件的架构,需要什么属性,怎么实现,这样设计会出现什么问题等等,就完全是摸着石头过河,一步步陷入深渊都不知道,这个三级联动我用了3天的时间才做完,还是开发的太少,相关方面的基础也不足,比如说js的作用域和闭包等。可以参考一些开源的 已经现有的组件库的源码加以巩固基础。
最后再附上项目网址 哈哈哈 轻喷! www.zphsow.ltd/aps/