Posts 组件实现思路
Post
Cancel

组件实现思路

总是用轮子,造轮子的思路是什么呢?

原则

(内部)分层原则:正交原则 (对外)封装原则:面向接口编程

分层原则:正交原则

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);
  }
}
This post is licensed under CC BY 4.0 by the author.
Trending Tags
Contents

Trending Tags