小莫的成神之旅(一)原生js封裝組件tooltip

小莫碎碎念css

小莫第一次寫技術博客無甚經驗,望諸位大神和小白同僚莫要見怪,鑑於但願小莫往後能不忘初衷,每篇博客開頭都有雷打不動的常設模塊「小莫碎碎念」,關注技術的同僚能夠繞過,這個模塊基本沒什麼有用的,咳咳。html

小莫最近在作的項目中用了ng2-bootstrap,常常會用到tooltip,但bootstrap的tooltip有一個缺憾,就是在鼠標懸浮在tip上的時候tip就消失了了,bootstrap的解釋是移動端不須要鼠標懸浮的功能,小莫從網上查了不少解決方案,但都是基於js的,沒有基於ts的,小莫才疏學淺又不肯意動源碼,固然也是爲了方便小莫學習原生js,小莫當下決定本身用js封裝一個tooltip組件。bootstrap

其實小莫在查閱資料的時候大多不會看實現思路和心路歷程之類的前提,都會直接看結果,相信不少同僚和小莫同樣只關注結果,但在封裝組件的過程當中小莫的確遇到了一些不大不小的問題,爲了方便記憶也爲了整理出一套思考問題的方法,小莫決定從頭至尾詳細記錄,只關注結果的同僚請繞過「實現思路」。app

好!碎碎唸完畢,進入主題!ide

 

實現思路學習

tooltip是一個相對比較簡單的功能,主要須要實現如下三個功能點:this

1.tip的彈出和隱藏;spa

2.tip彈出位置和內容自定義;.net

3.相關css實現。3d

 

第一條和第二條細分又可分爲如下幾點:

1.鼠標懸浮,彈出tip;

2.鼠標移開,隱藏tip;

3.鼠標移到tip上,tip不隱藏;

4.鼠標移出tip,tip隱藏;

5.從目標元素的上下左右彈出tip。

 

首先,要實現鼠標over和leave目標元素後,tip的顯示和隱藏,分別用onmouseenter和onmouseleave;而後,判斷鼠標是否over在tip區域,若是在tip上,tip不隱藏,不然反之,這裏要注意添加鼠標leave tip後,tip隱藏的事件;最後,肯定tip的彈出位置和內容。

 

最終代碼

 css不作贅述直接上代碼:

 1 .tooltip{position:absolute;max-width:300px;z-index:999;}
 2 
 3 .tip-content{border:1px solid gray;border-radius: 6px;box-shadow: 0 5px 10px rgba(0,0,0,.2);padding:5px;background-color: #ffffff;}
 4 .tip-arrow{
 5     position:absolute;
 6     border:7px solid transparent;
 7 }
 8 .tip-arrow:after{
 9     position:absolute;
10     content:'';
11     width:0;height:0;
12     box-sizing:border-box;
13     border:7px solid transparent;
14 }
15 
16 .tip-content.top{
17     margin-bottom:7px;
18 }
19 .tip-arrow.top{
20     border-bottom-width:0;bottom: 0;left:50%;margin-left: -7px;
21     border-top-color:gray;
22 }
23 .tip-arrow.top:after{
24     margin-left:-7px;
25     margin-top:-8px;
26     border-bottom-width:0;
27     border-top-color:#fff;
28 }
29 
30 .tip-content.bottom{
31     margin-top:7px;
32 }
33 .tip-arrow.bottom{
34     border-top-width:0;top: 0;left:50%;margin-left: -7px;
35     border-bottom-color:gray;
36 }
37 .tip-arrow.bottom:after{
38     margin-left:-7px;
39     margin-top:1px;
40     border-top-width:0;
41     border-bottom-color:#fff;
42 }
43 
44 .tip-content.left{
45     margin-right:7px;
46 }
47 .tip-arrow.left{
48     border-right-width:0;right: 0;top:50%;margin-top: -7px;
49     border-left-color:gray;
50 }
51 .tip-arrow.left:after{
52     margin-left:-8px;
53     margin-top:-7px;
54     border-right-width:0;
55     border-left-color:#fff;
56 }
57 
58 .tip-content.right{
59     margin-left:7px;
60 }
61 .tip-arrow.right{
62     border-left-width:0;left: 0;top:50%;margin-top: -7px;
63     border-right-color:gray;
64 }
65 .tip-arrow.right:after{
66     margin-left:1px;
67     margin-top:-7px;
68     border-left-width:0;
69     border-right-color:#fff;
70 }
tooltip css

 js也直接上代碼:

 1 function getElementPos(el) {
 2             let _x = 0, _y = 0;
 3             do {
 4                 _x += el.offsetLeft;
 5                 _y += el.offsetTop;
 6             } while (el = el.offsetParent);
 7             return { x: _x, y: _y };
 8         }
 9 
10         var ToolTip = function(){
11             var obj = {};
12             obj.tip = null;
13             obj.showTip = function(el,html){
14                 if(obj.tip){
15                     return;
16                 }
17                 var elPos = getElementPos(el);
18                 var posi = el.getAttribute('posi')?el.getAttribute('posi'):"top";
19                 var tip = document.createElement("div");
20                 tip.className = 'tooltip';
21                 var arrow = document.createElement("div");
22                 arrow.className = 'tip-arrow '+posi;
23                 var content = document.createElement("div");
24                 content.className = 'tip-content '+posi;
25                 tip.appendChild(content);
26                 tip.appendChild(arrow);
27                 content.innerHTML = html;
28                 document.body.appendChild(tip);
29                 switch (posi){
30                     case "top":{
31                         tip.style.top = (elPos.y - content.offsetHeight - 7) + 'px';
32                         tip.style.left = (elPos.x + el.offsetWidth/2 - content.offsetWidth/2) + 'px';
33                     }
34                         break;
35                     case "bottom":{
36                         tip.style.top = (elPos.y + el.offsetHeight) + 'px';
37                         tip.style.left = (elPos.x + el.offsetWidth/2 - content.offsetWidth/2) + 'px';
38                     }
39                         break;
40                     case "left":{
41                         tip.style.top = (elPos.y + el.offsetHeight/2 - content.offsetHeight/2) + 'px';
42                         tip.style.left = (elPos.x - content.offsetWidth - 7) + 'px';
43                     }
44                         break;
45                     case "right":{
46                         tip.style.top = (elPos.y + el.offsetHeight/2 - content.offsetHeight/2) + 'px';
47                         tip.style.left = (elPos.x + el.offsetWidth) + 'px';
48                     }
49                         break;
50                 }
51 
52                 tip.addEventListener("mouseenter",function(){
53                     tip.setAttribute("in",true);
54                 });
55                 tip.addEventListener("mouseleave",function(){
56                     tip.setAttribute("in",false);
57                     obj.hideTip();
58                 });
59 
60                 obj.tip = tip;
61             };
62             obj.hideTip = function () {
63                 if(this.tip && this.tip.getAttribute("in") != "true"){
64                     document.body.removeChild(this.tip);
65                     obj.tip = null;
66                 }
67             };
68             obj.init = function(el){
69                 el.onmouseenter = function(){obj.showTip(this, this.getAttribute('tipContent'))};
70                 el.onmouseleave = function(){
71                     setTimeout(function(){
72                         obj.hideTip();
73                     },0);
74 
75                 };
76             };
77 
78             return obj;
79         }
tooltip js

 html代碼(有的時候小莫這樣的小白麪對封裝好的組件卻不會用,那心情至關沉重):

 1 <div class="container" posi = "bottom" name="tip" tipContent="this <br> <button>diandian</button>  is a long long long long long <br> long long long long long long tool tip.">hover me</div>
 2 <div class="container" posi = "left" name="tip" tipContent="this is a tool tip2.">hover me2</div>
 3 <div class="container" posi = "right" name="tip" tipContent="this <br>  is a long long long long long <br> long long long long long long tool tip3.">hover me3</div>
 4 <div class="container" posi = "top" name="tip" tipContent="this is a tool tip4.">hover me4</div>
 5 
 6 <script>
 7     var conArr = document.getElementsByName("tip");
 8     if(conArr) {
 9         for (var i = 0; i < conArr.length; i++) {
10             var tip = new ToolTip();
11             tip.init(conArr[i]);
12         }
13     }
14 </script>
tooltip html

 順便附上ts版tooltip:

 1 import {Directive, ElementRef, Input, HostListener} from "@angular/core";
 2 
 3 @Directive({
 4     selector: '[tooltip]'
 5 })
 6 export class ToolTip{
 7 
 8     @Input('tooltip')
 9     tooltipCon: string;
10 
11     private tip;
12 
13     constructor(private elRef: ElementRef) {}
14 
15     @HostListener('mouseenter') onMouseEnter() {
16         this.show();
17     }
18 
19     @HostListener('mouseleave') onMouseLeave() {
20         let self = this;
21         setTimeout(function(){
22             self.hide();
23         },0);
24     }
25 
26     show(){
27         let el = this.elRef.nativeElement as HTMLElement;
28         if(this.tip){
29             return;
30         }
31         let posi = el.getAttribute('posi')?el.getAttribute('posi'):"top";
32         let elPos = this.getElementPos(el);
33         let self = this;
34 
35         let tip = document.createElement("div");
36         tip.className = 'tip-container';
37         let arrow = document.createElement("div");
38         arrow.className = 'tip-arrow '+posi;
39         let content = document.createElement("div");
40         content.className = 'tip-content '+posi;
41         tip.appendChild(content);
42         tip.appendChild(arrow);
43         content.innerHTML = this.tooltipCon;
44         document.body.appendChild(tip);
45         tip.style.top = (elPos.y - content.offsetHeight - 10) + 'px';
46         tip.style.left = (elPos.x + el.offsetWidth/2 - content.offsetWidth/2) + 'px';
47 
48         switch (posi){
49             case "top":{
50                 tip.style.top = (elPos.y - content.offsetHeight - 7) + 'px';
51                 tip.style.left = (elPos.x + el.offsetWidth/2 - content.offsetWidth/2) + 'px';
52             }
53                 break;
54             case "bottom":{
55                 tip.style.top = (elPos.y + el.offsetHeight) + 'px';
56                 tip.style.left = (elPos.x + el.offsetWidth/2 - content.offsetWidth/2) + 'px';
57             }
58                 break;
59             case "left":{
60                 tip.style.top = (elPos.y + el.offsetHeight/2 - content.offsetHeight/2) + 'px';
61                 tip.style.left = (elPos.x - content.offsetWidth - 7) + 'px';
62             }
63                 break;
64             case "right":{
65                 tip.style.top = (elPos.y + el.offsetHeight/2 - content.offsetHeight/2) + 'px';
66                 tip.style.left = (elPos.x + el.offsetWidth) + 'px';
67             }
68                 break;
69         }
70 
71         tip.addEventListener("mouseenter",function(){
72             tip.setAttribute("in","true");
73         });
74         tip.addEventListener("mouseleave",function(){
75             tip.setAttribute("in","false");
76             self.hide();
77         });
78 
79         this.tip = tip;
80     }
81 
82     hide(){
83         if(this.tip && this.tip.getAttribute("in") != "true"){
84             document.body.removeChild(this.tip);
85             this.tip = null;
86         }
87     }
88 
89     getElementPos(el) {
90         let _x = 0, _y = 0;
91         do {
92             _x += el.offsetLeft;
93             _y += el.offsetTop;
94         } while (el = el.offsetParent);
95         return { x: _x, y: _y };
96     }
97 }
ts版

按照通常指令使用便可,使用方法小莫就不在贅述了:

 1 <div tooltip="this is a tool tip." posi = "bottom">
 2     hover me
 3 </div>
 4 <div tooltip="this is a tool tip2." posi = "left">
 5     hover me2
 6 </div>
 7 <div tooltip="this is a tool tip3." posi = "right">
 8     hover me3
 9 </div>
10 <div tooltip="this is a tool tip4." posi = "top">
11     hover me4
12 </div>

只關注結果的童鞋能夠直接右拐直行了,下面都是代碼分析啦!

 

代碼分析

css沒什麼可說的,只是一些小技巧,給箭頭元素添加一個背景色爲白的:after元素,再控制一下位置,就能將箭頭的邊框畫出來了,除此以外你們想必都很熟悉了,咱們仍是着重說js。

1 function getElementPos(el) {
2     let _x = 0, _y = 0;
3     do {
4         _x += el.offsetLeft;
5         _y += el.offsetTop;
6     } while (el = el.offsetParent);
7     return { x: _x, y: _y };
8 }

這個方法用於獲取目標元素左上角相對於窗體的座標,因爲offsetLeft和offsetTop只能用於獲取元素相對於父元素的位置,因此須要循環獲取父元素的座標直至根元素。

咱們先從初始化提及,給目標元素添加over和leave事件:

1             obj.init = function(el){
2                 el.onmouseenter = function(){obj.showTip(this, this.getAttribute('tipContent'))};
3                 el.onmouseleave = function(){
4                     setTimeout(function(){
5                         obj.hideTip();
6                     },0);
7 
8                 };
9             };

這裏提一句,動態給元素添加事件的方法基本有三個,感興趣的小盆友請猛戳這裏:點我點我點我。添加setTimeout是爲了將這段代碼放到後面執行,也就是說須要在給tip元素的in屬性賦值後執行,不然會先執行hide方法再給tip元素的in屬性賦值(那麼這個賦值就沒有任何意義了,由於咱們在隱藏tip的時候要判斷tip元素的in屬性是否爲true)。

而後是重頭戲tip的顯示。

 1 obj.showTip = function(el,html){
 2                 //若是當前目標元素的tip已經顯示,返回,避免重複生成tip元素。
 3                 if(obj.tip){
 4                     return;
 5                 }
 6                 //獲取目標元素座標
 7                 var elPos = getElementPos(el);
 8                 //獲取tip彈出位置
 9                 var posi = el.getAttribute('posi')?el.getAttribute('posi'):"top";
10 
11                 //建立tip元素
12                 var tip = document.createElement("div");
13                 tip.className = 'tooltip';
14                 //建立箭頭元素
15                 var arrow = document.createElement("div");
16                 arrow.className = 'tip-arrow '+posi;
17                 //建立內容元素
18                 var content = document.createElement("div");
19                 content.className = 'tip-content '+posi;
20                 //給tip元素添加箭頭元素和內容元素
21                 tip.appendChild(content);
22                 tip.appendChild(arrow);
23                 content.innerHTML = html;
24                 //將tip元素添加到body,必須先將元素添加到body,後面的代碼纔會生效
25                 document.body.appendChild(tip);
26                 //根據不一樣彈出位置肯定tip的座標
27                 switch (posi){
28                     case "top":{
29                         tip.style.top = (elPos.y - content.offsetHeight - 7) + 'px';
30                         tip.style.left = (elPos.x + el.offsetWidth/2 - content.offsetWidth/2) + 'px';
31                     }
32                         break;
33                     case "bottom":{
34                         tip.style.top = (elPos.y + el.offsetHeight) + 'px';
35                         tip.style.left = (elPos.x + el.offsetWidth/2 - content.offsetWidth/2) + 'px';
36                     }
37                         break;
38                     case "left":{
39                         tip.style.top = (elPos.y + el.offsetHeight/2 - content.offsetHeight/2) + 'px';
40                         tip.style.left = (elPos.x - content.offsetWidth - 7) + 'px';
41                     }
42                         break;
43                     case "right":{
44                         tip.style.top = (elPos.y + el.offsetHeight/2 - content.offsetHeight/2) + 'px';
45                         tip.style.left = (elPos.x + el.offsetWidth) + 'px';
46                     }
47                         break;
48                 }
49 
50                 //當鼠標進入tip區域,將屬性in設置爲true
51                 tip.addEventListener("mouseenter",function(){
52                     tip.setAttribute("in",true);
53                 });
54                 //當鼠標離開tip區域,將屬性in設置爲false,同時隱藏tip
55                 tip.addEventListener("mouseleave",function(){
56                     tip.setAttribute("in",false);
57                     obj.hideTip();
58                 });
59 
60                 //將tip元素賦值給obj,以判斷當前目標元素是否已經擁有tip元素,同時在hide的時候判斷當前須要移出的是哪一個tip元素
61                 obj.tip = tip;
62             };

隱藏tip就相對簡單多啦:

1             obj.hideTip = function () {
2                 //判斷當前目標元素的tip元素的in屬性,也就是判斷鼠標是否在tip區域內
3                 if(this.tip && this.tip.getAttribute("in") != "true"){
4                     document.body.removeChild(this.tip);
5                     //將當前目標元素的tip元素置爲null
6                     obj.tip = null;
7                 }
8             };

組件寫完啦,下面看看怎麼使用它:

1     //遍歷全部tooltip的目標元素,而後初始化
2     var conArr = document.getElementsByName("tip");
3     if(conArr) {
4         for (var i = 0; i < conArr.length; i++) {
5             var tip = new ToolTip();
6             tip.init(conArr[i]);
7         }
8     }

原本小莫並無每個目標元素對應一個tip元素,但後來發現當目標元素都彙集在一塊兒的時候(比方說表格)會出現tip閃爍或者不顯示的bug,究其緣由是多個目標元素共用一個tip,而hide方法放到了setTimeout中,致使下一個tip元素剛生成就被hide了,因此要求每個目標元素對應一個tip元素。

大功告成!

 

總結

雖然是小莫原生js封裝組件的處女座,但彷彿沒有什麼可總結的,但這個環節必需要有!ok,多謝能堅持到最後的各位同僚!小莫拜謝!

相關文章
相關標籤/搜索