总是用轮子,造轮子的思路是什么呢?
原则
(内部)分层原则:正交原则 (对外)封装原则:面向接口编程
分层原则:正交原则
HTML、CSS、JS 三者分离。
例如:不要用 JS 直接去改 CSS,而是增加一个 class,具体的样式还是由 CSS 提供。
封装原则:面向接口编程
首要考虑的不是代码如何写,而是代码如何被调用。
五个轮子
Tabs
对外接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <body>
<div class="tabs">
<ol class="tabs-bar">
<li>1</li>
<li>2</li>
<li>3</li>
</ol>
<ol class="tabs-content">
<li>content 1</li>
<li>content 2</li>
<li>content 3</li>
</ol>
</div>
</body>
<script>
var tabs = new Tabs(".tabs");
</script>
|
代码实现
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Tabs</title>
<style>
.tabs {
}
.tabs > ol {
list-style: none;
margin: 0;
padding: 0;
}
.tabs > ol.tabs-bar {
display: flex;
border-bottom: 1px solid red;
}
.tabs > ol.tabs-bar > li {
padding: 4px 8px;
border: 1px solid transparent;
border-bottom: 0;
}
.tabs > ol.tabs-bar > li:hover {
border-color: red;
}
.tabs > ol.tabs-bar > li.active {
border-color: blue;
}
.tabs > ol.tabs-content > li {
display: none;
}
.tabs > ol.tabs-content > li.active {
display: block;
}
</style>
</head>
<body>
<div class="tabs">
<ol class="tabs-bar">
<li>1</li>
<li>2</li>
<li>3</li>
</ol>
<ol class="tabs-content">
<li>content 1</li>
<li>content 2</li>
<li>content 3</li>
</ol>
</div>
<script>
function Tabs(selector) {
this.el = $(selector);
this.el.each(function (index, el) {
$(el).children(".tabs-bar").children("li").eq(0).addClass("active");
$(el)
.children(".tabs-content")
.children("li")
.eq(0)
.addClass("active");
});
this.el.on("click", ".tabs-bar > li", function (e) {
var $li = $(e.currentTarget);
$li.addClass("active").siblings().removeClass("active");
var index = $li.index();
var $content = $li
.closest(".tabs")
.find(".tabs-content>li")
.eq(index);
$content.addClass("active").siblings().removeClass("active");
});
}
var tabs = new Tabs(".tabs");
</script>
</body>
</html>
|
JS 代码优化
ES5 版本
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
| function Tabs(selector) {
this.el = $(selector);
this.init();
this.bindEvents();
}
Tabs.prototype.init = funtion() {
this.el.each(function(index, el) {
$(el).children('.tabs-bar').children('li').eq(0).addClass('active');
$(el).children('.tabs-content').children('li').eq(0).addClass('active');
});
}
Tabs.prototype.bindEvents = function() {
this.el.on('click', '.tabs-bar > li', function(e) {
var $li = $(e.currentTarget);
$li.addClass('active')
.siblings()
.removeClass('active');
var index = $li.index();
var $content = $li.closest('.tabs')
.find('.tabs-content>li')
.eq(index);
$content.addClass('active')
.siblings()
.removeClass('active');
});
}
|
ES6 版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| class Tabs {
constructor(selector) {
this.el = $(selector);
this.init();
this.bindEvents();
}
init() {
this.el.each(function (index, el) {
$(el).children(".tabs-bar").children("li").eq(0).addClass("active");
$(el).children(".tabs-content").children("li").eq(0).addClass("active");
});
}
bindEvents() {
this.el.on("click", ".tabs-bar > li", function (e) {
var $li = $(e.currentTarget);
$li.addClass("active").siblings().removeClass("active");
var index = $li.index();
var $content = $li.closest(".tabs").find(".tabs-content>li").eq(index);
$content.addClass("active").siblings().removeClass("active");
});
}
}
|
Sticky
对外接口
1
2
3
| <script>
var sticky = new Sticky("#topbar", 0);
</script>
|
代码实现
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
| class Sticky {
constructor(selector, n) {
this.elements = $(selector);
this.offset = n;
this.cachedOffsets = [];
this.addPlaceholder();
this.cacheOffsets();
this.listenToScroll();
}
addPlaceholder() {
this.elements.each((index, element) => {
$(element).wrap('<div class="sticky-placeholder"></div>');
$(element).parent().height($(element).height());
});
}
cacheOffsets() {
this.elements.each((index, element) => {
this.offsets[index] = $(element).offset();
});
}
listenToScroll() {
$(window).on("scroll", () => {
var scrollY = window.scrollY;
this.elements.each((index, element) => {
var $element = $(element);
if (scrollY + this.offset > this.offsets[index].top) {
$element.addClass("sticky").css({ top: this.offset }); // 这句话违反了正交原则
} else {
$element.removeClass("sticky");
}
});
});
}
}
|
1
2
3
4
5
| .sticky {
position: fixed;
left: 0;
top: 0;
}
|
Dialog
对外接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| button.onclick = function () {
var dialog = new dialog({
title: "标题",
content: "<b>欢迎</b>",
className: "user-class",
buttons: [
{ text: "确定", action: function () {} },
{
text: "取消",
action: function () {
dialog.close();
},
},
],
});
dialog.open(); // dialog.close();
};
|
代码实现
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
| class Dialog {
constructor(options) {
this.options = options;
this.init();
}
get template() {
let { title, content } = this.options;
let template = `
<div class="ring-dialog">
<div class="ring-wrapper">
<header class="ring-header">${title}</header>
<main class="ring-main">${content}</main>
<footer class="ring-footer"></footer>
</div>
</div>
`;
}
generateButtons() {
let { buttons } = this.options;
let buttons = buttons.map((buttonOptions) => {
let $b = $("<button></button>");
$b.text(buttonOptions.text);
$b.on("click", buttonOptions.action);
return $b;
});
return buttons;
}
init() {
let $dialog = $(this.template);
$dialog.find("footer").append(this.generateButtons());
$dialog.addClass(this, options.className);
this.$dialog = $dialog;
}
open() {
this.$dialog.appendTo("body");
}
close() {
this.$dialog.detach();
}
}
|
Suggestion
对外接口
1
2
3
4
5
6
7
8
| var suggestion = new Suggestion({
input: "#x",
search: function (text, callback) {
callback(["a", "aa", "ab", "abc"]);
},
emptyTemplate: "<b>没有结果</b>",
loadingTemplate: "<b>正在加载中...</b>",
});
|
代码实现
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
| class Suggestion {
constructor(options) {
this.options = options;
this.$input = $(options.input);
this.$input.wrap('<div class="suggsetion"></div>');
this.$wrapper = this.$input.parent();
this.$ol = $("<ol></ol>");
this.$input.after(this.$ol);
this.$loading = $('<div class="suggestion-loading"></div>');
this.$loading.html(this.options.loadingTemplate);
this.$ol.after(this.$loading);
this.bindEvents();
}
bindEvents() {
let timerId;
this.$input.on("input", (e) => {
if (timerId) {
clearInterval(timerId);
}
timerId = setTimeout(() => {
this.search(e.currentTarget.value);
}, 1000);
});
this.$ol.on("click", "li", (e) => {
this.$input.value($(e.currentTarget).text());
});
}
search(keyword) {
this.$wrapper.addClass("loading");
this.options.search(keyword, (array) => {
this.$ol.empty();
this.$wrapper.removeClass("empty");
if (!array || array.length === 0) {
this.$wrapper.addClass("empty");
return;
}
array.forEach((text) => {
$ol.append($("<li></li>").text(text));
});
});
}
}
// 其余部分要写 CSS 实现
|
Slides
对外接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| <body>
<div class="slides">
<ol>
<li><img src="" alt="" /></li>
<li><img src="" alt="" /></li>
<li><img src="" alt="" /></li>
</ol>
</div>
</body>
<script>
var slide = new Slide({
element: ".slides",
autoPlay: false,
controls: false,
pager: false,
});
slide.go(2);
slide.next();
slide.prev();
slide.play();
slide.stop();
</script>
|
代码实现
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
| class Slide {
constructor(options) {
this.options = options;
this.$element = $(this.options.element);
this.$element.addClass("slides");
this.timer = undefined;
this.current = 0;
this.initHtml();
this.bindEvents();
this.go(0);
if (this.options.autoPlay) {
this.play();
}
}
initHtml() {
var width = this.$element.children("ol").children("li").width();
this.$element.width(width);
this.$prev = $('<button class="slides-prev">上一张</button>');
this.$element.append(this.$prev);
this.$next = $('<button class="slides-next">下一张</button>');
this.$element.append(this.$next);
}
bindEvents() {
this.$prev.on("click", () => this.prev());
this.$next.on("click", () => this.next());
this.$element
.on("mouseenter", () => {
this.stop();
})
.on("mouseleave", () => {
this.play();
});
}
go(index) {
let $ol = this.$element.children("ol");
let $items = $ol.children("li");
if (index >= $items.length) {
index = 0;
} else if (index < 0) {
index = $items.length - 1;
}
$ol.css({ transform: `translateX(${-index * 400}px)` });
this.current = index;
}
next() {
this.go(this.current + 1);
}
prev() {
this.go(this.current - 1);
}
play() {
this.timer = setInterval(() => {
this.go(this.current + 1);
}, 2000);
}
stop() {
clearInterval(this.timer);
}
}
|