最近UI上經常遇到點擊一個按鈕,出現一個帶指向箭頭的面板的情況,如圖:
於是就寫了個flyout-panel的組件,但此flyout-panel(飛出面板)只是個點擊面板,更像tooltip,不是嚴格意義上的飛出面板(如我博客左側“分享到...”面板),只是不太會取名字,估且先叫flyout-panel;
先上代碼,再講邏輯:
(function (){
var reqs = [
"Handlebars",
"jquery",
"text!mr-fp/flyout-panel.html", // 支持行內樣式時需要的模板
"css!mr-fp/flyout-panel.css" // 支持外鏈樣式時需要的樣式
];
define(reqs , function (Handlebars,$,fpHTML) {
var FlyOutPanel = function(options){
var styles = { // 面板默認樣式,參數名顧名思義,標*號為必選參數
"panelClass" : "flyout-panel",
"borderWidth" : "1px",
"borderColor" : "#000",
"bgColor" : "#fff",
"panelWidth" : "100px", // *
"panelHeight" : "100px",
"arrowLeft" : "50%", // *arrowLeft與arrowRight是互斥的(這里有點別扭)
"arrowRight" : "auto", // *當前三角箭頭相對於面板的位置是left:50%
"arrowBorderWidth" : "10px", // *三角箭頭的邊寬,該三角形所在的div元素現為:20px*20px
"contentPadding" : "auto"
};
this.options = {
"id": "",
"styleInline" : true, // 默認支持行內樣式
"container" : document.body, // default is document.body, a dom element;
"content" : null, // 面板中的內容元素,jquery元素
"triggerBtn" : null, // 點擊顯示面板的button,juqery元素,如果未傳,則要顯式的為相應按鈕綁定顯示panel事件
"styles" : styles,
"timer" : 1000 // 默認面板顯示1秒后隱藏
};
$.extend( true, this.options,options);
this.styles = this.options.styles;
this.hideTimer = null;
this._create();
};
FlyOutPanel.prototype = {
_create : function(){
var tpl;
if( this.options.styleInline){
var source = $(fpHTML).html();
var template = Handlebars.compile(source);
tpl = template( this.styles);
} else{
tpl = ['<div>',
'<div class="fp-arrow">',
'<div class="fp-a1"></div>',
'<div class="fp-a2"></div>',
'</div>',
'<div class="fp-content"></div>',
'</div>'].join('');
}
var _el = this._el = $(tpl).addClass( this.styles.panelClass);
_el.unselectable();
this.containerEl = $( this.options.container).append(_el);
this.arrowEl = _el.find('.fp-arrow');
_el.width( this.styles.panelWidth);
_el.height( this.styles.panelHeight);
this.contentEl = _el.find('.fp-content').append( this.options.content.show());
this.triggerBtn = $( this.options.triggerBtn);
this.bindEvents();
},
setPosition : function(offset){ // 設置面板的位置以及三角箭頭的位置
var styles = this.styles;
var _el = this._el;
var _w= _el.width() + parseInt(styles.borderWidth)*2;
var arrowLeftPos = _w/2;
var arrowLeft = styles.arrowLeft,
arrowRight = styles.arrowRight,
_arrowBorderWidth = parseInt(styles.arrowBorderWidth,"10");
console.log(arrowLeft + arrowRight);
if(!isNaN(parseInt(arrowLeft,10))){
var _lv = parseInt(arrowLeft,10);
if(arrowLeft.indexOf("%")>0){
arrowLeftPos = _w*_lv/100;
} else{
arrowLeftPos = _lv;
}
}
if(!isNaN(parseInt(arrowRight,10))){
var _rv = parseInt(arrowRight,10);
if(arrowRight.indexOf("%")>0){
arrowLeftPos = _w*(100-_rv)/100;
} else{
arrowLeftPos = _w - _rv;
}
}
if(_w - arrowLeftPos - _arrowBorderWidth <0){
arrowLeftPos = _w - _arrowBorderWidth;
this.arrowEl.css("right",_arrowBorderWidth + "px").css("left","auto");
}
_el.css({"top":offset.top + _arrowBorderWidth,"left":offset.left - arrowLeftPos});
_el.show();
this.hideTimer = setTimeout( function(){
_el.hide();
}, this.options.timer);
},
bindEvents : function(){
var self = this,
_el = this._el;
_el.bind('mouseleave.hide', function(){
self.hideTimer = setTimeout( function(){
_el.hide();
},self.options.timer);
}).bind('mouseenter', function(){
clearTimeout(self.hideTimer);
});
var btn = this.triggerBtn;
if(btn && btn.length){
btn.bind('click.show', function(){
var pos = calculatePosition(btn);
self.setPosition(pos);
});
}
},
setArrowPositon : function(){
},
};
function calculatePosition($btn){
var offset = $btn.offset(),
top = offset.top + $btn.height(),
left = offset.left + $btn.width() /2;
return {'top' : top,'left' : left};
}
return FlyOutPanel;
});
})();
"Handlebars",
"jquery",
"text!mr-fp/flyout-panel.html", // 支持行內樣式時需要的模板
"css!mr-fp/flyout-panel.css" // 支持外鏈樣式時需要的樣式
];
define(reqs , function (Handlebars,$,fpHTML) {
var FlyOutPanel = function(options){
var styles = { // 面板默認樣式,參數名顧名思義,標*號為必選參數
"panelClass" : "flyout-panel",
"borderWidth" : "1px",
"borderColor" : "#000",
"bgColor" : "#fff",
"panelWidth" : "100px", // *
"panelHeight" : "100px",
"arrowLeft" : "50%", // *arrowLeft與arrowRight是互斥的(這里有點別扭)
"arrowRight" : "auto", // *當前三角箭頭相對於面板的位置是left:50%
"arrowBorderWidth" : "10px", // *三角箭頭的邊寬,該三角形所在的div元素現為:20px*20px
"contentPadding" : "auto"
};
this.options = {
"id": "",
"styleInline" : true, // 默認支持行內樣式
"container" : document.body, // default is document.body, a dom element;
"content" : null, // 面板中的內容元素,jquery元素
"triggerBtn" : null, // 點擊顯示面板的button,juqery元素,如果未傳,則要顯式的為相應按鈕綁定顯示panel事件
"styles" : styles,
"timer" : 1000 // 默認面板顯示1秒后隱藏
};
$.extend( true, this.options,options);
this.styles = this.options.styles;
this.hideTimer = null;
this._create();
};
FlyOutPanel.prototype = {
_create : function(){
var tpl;
if( this.options.styleInline){
var source = $(fpHTML).html();
var template = Handlebars.compile(source);
tpl = template( this.styles);
} else{
tpl = ['<div>',
'<div class="fp-arrow">',
'<div class="fp-a1"></div>',
'<div class="fp-a2"></div>',
'</div>',
'<div class="fp-content"></div>',
'</div>'].join('');
}
var _el = this._el = $(tpl).addClass( this.styles.panelClass);
_el.unselectable();
this.containerEl = $( this.options.container).append(_el);
this.arrowEl = _el.find('.fp-arrow');
_el.width( this.styles.panelWidth);
_el.height( this.styles.panelHeight);
this.contentEl = _el.find('.fp-content').append( this.options.content.show());
this.triggerBtn = $( this.options.triggerBtn);
this.bindEvents();
},
setPosition : function(offset){ // 設置面板的位置以及三角箭頭的位置
var styles = this.styles;
var _el = this._el;
var _w= _el.width() + parseInt(styles.borderWidth)*2;
var arrowLeftPos = _w/2;
var arrowLeft = styles.arrowLeft,
arrowRight = styles.arrowRight,
_arrowBorderWidth = parseInt(styles.arrowBorderWidth,"10");
console.log(arrowLeft + arrowRight);
if(!isNaN(parseInt(arrowLeft,10))){
var _lv = parseInt(arrowLeft,10);
if(arrowLeft.indexOf("%")>0){
arrowLeftPos = _w*_lv/100;
} else{
arrowLeftPos = _lv;
}
}
if(!isNaN(parseInt(arrowRight,10))){
var _rv = parseInt(arrowRight,10);
if(arrowRight.indexOf("%")>0){
arrowLeftPos = _w*(100-_rv)/100;
} else{
arrowLeftPos = _w - _rv;
}
}
if(_w - arrowLeftPos - _arrowBorderWidth <0){
arrowLeftPos = _w - _arrowBorderWidth;
this.arrowEl.css("right",_arrowBorderWidth + "px").css("left","auto");
}
_el.css({"top":offset.top + _arrowBorderWidth,"left":offset.left - arrowLeftPos});
_el.show();
this.hideTimer = setTimeout( function(){
_el.hide();
}, this.options.timer);
},
bindEvents : function(){
var self = this,
_el = this._el;
_el.bind('mouseleave.hide', function(){
self.hideTimer = setTimeout( function(){
_el.hide();
},self.options.timer);
}).bind('mouseenter', function(){
clearTimeout(self.hideTimer);
});
var btn = this.triggerBtn;
if(btn && btn.length){
btn.bind('click.show', function(){
var pos = calculatePosition(btn);
self.setPosition(pos);
});
}
},
setArrowPositon : function(){
},
};
function calculatePosition($btn){
var offset = $btn.offset(),
top = offset.top + $btn.height(),
left = offset.left + $btn.width() /2;
return {'top' : top,'left' : left};
}
return FlyOutPanel;
});
})();
調用方式:
_createFlyoutPanels :
function(){
var self = this;
var _el = this.el;
var $eyePanel = _el.find('.eye-panel'),
$eyeBtn = this._settingEl.find('.eye-btn');
this.eyePanel = new FlyoutPanel({
"styleInline" : false,
'container' : _el,
'content' : $eyePanel,
'triggerBtn' : $eyeBtn,
'styles' : {
'panelWidth' : "160px",
'panelHeight' : "100px",
}
});
},
var self = this;
var _el = this.el;
var $eyePanel = _el.find('.eye-panel'),
$eyeBtn = this._settingEl.find('.eye-btn');
this.eyePanel = new FlyoutPanel({
"styleInline" : false,
'container' : _el,
'content' : $eyePanel,
'triggerBtn' : $eyeBtn,
'styles' : {
'panelWidth' : "160px",
'panelHeight' : "100px",
}
});
},
UI邏輯:
1,動態的創建一個帶箭頭的面板,將要顯示的內容append進面板;
2,綁定面板事件,主要是mouseleave,mouseenter事件
3,綁定按鈕事件,主要指點擊button,計算面板位置和箭頭位置,顯示面板;
創建帶箭頭的面板
箭頭可以用圖片實現,這里用dom來模擬,模擬方式大致是這樣的:
http://cssarrowplease.com/,
考慮到主要是為了支持行內樣式,所以就用兩個div來代替:before,:after偽類生成的dom。
兩種方式生成面板的Dom結構:
var tpl;
if( this.options.styleInline){//行內樣式Dom結構
var source = $(fpHTML).html();
var template = Handlebars.compile(source);
tpl = template( this.styles);
} else{//外部樣式Dom結構
tpl = ['<div>',
'<div class="fp-arrow">',
'<div class="fp-a1"></div>',
'<div class="fp-a2"></div>',
'</div>',
'<div class="fp-content"></div>',
'</div>'].join('');
}
if( this.options.styleInline){//行內樣式Dom結構
var source = $(fpHTML).html();
var template = Handlebars.compile(source);
tpl = template( this.styles);
} else{//外部樣式Dom結構
tpl = ['<div>',
'<div class="fp-arrow">',
'<div class="fp-a1"></div>',
'<div class="fp-a2"></div>',
'</div>',
'<div class="fp-content"></div>',
'</div>'].join('');
}
行內樣式方式的html模板(handlebars template):
<
script
id
="fp-template"
type
="text/x-handlebars-template"
>
<div class="{{panelClass}}" style="position:fixed;border:{{borderWidth}} solid {{borderColor}};background-color:{{bgColor}};width:{{panelWidth}};height:{{panelHeight}};display:none;-moz-box-sizing : border-box;box-sizing : border-box;">
<div class="fp-arrow" style="position:absolute;width:0;left:{{arrowLeft}};right:{{arrowRight}};">
<div class="fp-a1" style="position:absolute;border:solid transparent;height:0;width:0;bottom:0;border-bottom-color:{{borderColor}};border-width:{{arrowBorderWidth}};left:50%;margin-left:-{{arrowBorderWidth}}"></div>
<div class="fp-a2" style="position:absolute;border:solid transparent;height:0;width:0;bottom:-{{borderWidth}};border-bottom-color:{{bgColor}};border-width:{{arrowBorderWidth}};left:50%;margin-left:-{{arrowBorderWidth}}"></div>
</div>
<div class="fp-content" style="padding:{{contentPadding}}"></div>
</div>
</ script >
<div class="{{panelClass}}" style="position:fixed;border:{{borderWidth}} solid {{borderColor}};background-color:{{bgColor}};width:{{panelWidth}};height:{{panelHeight}};display:none;-moz-box-sizing : border-box;box-sizing : border-box;">
<div class="fp-arrow" style="position:absolute;width:0;left:{{arrowLeft}};right:{{arrowRight}};">
<div class="fp-a1" style="position:absolute;border:solid transparent;height:0;width:0;bottom:0;border-bottom-color:{{borderColor}};border-width:{{arrowBorderWidth}};left:50%;margin-left:-{{arrowBorderWidth}}"></div>
<div class="fp-a2" style="position:absolute;border:solid transparent;height:0;width:0;bottom:-{{borderWidth}};border-bottom-color:{{bgColor}};border-width:{{arrowBorderWidth}};left:50%;margin-left:-{{arrowBorderWidth}}"></div>
</div>
<div class="fp-content" style="padding:{{contentPadding}}"></div>
</div>
</ script >
外部樣式方式則提供一個flyout-panel.less樣式文件來動態生成樣式:
@borderColor: #000;
@borderWidth: 5px;
@bgColor : #fff;
@arrowBorderWidth : 10px;
@panelWidth : 100px;
@panelHeight : 100px;
@arrowOffset : 1px;
@arrowBorderWidth : 10px;
@arrowLeft : 20%;
@arrowRight : auto;
@contentPadding : auto;
.flyout-panel{
position: fixed;
border: @borderWidth solid @borderColor;
display: none;
background: @bgColor;
width : @panelWidth;
height: @panelHeight;
.fp-arrow {
position: fixed;
width : 0;
left : @arrowLeft;
right: @arrowRight;
>div{
position: absolute;
border: solid transparent;
height: 0;
width: 0;
bottom: 0;
}
.fa-a1{
border-bottom-color : @borderColor;
border-width : @arrowBorderWidth;
left: 50%;
margin-left: -@arrowBorderWidth;
}
.fa-a2{
border-bottom-color : @bgColor;
border-width : @arrowBorderWidth;
left: 50%;
top: @borderWidth;
margin-left: -@arrowBorderWidth;
}
}
}
@borderWidth: 5px;
@bgColor : #fff;
@arrowBorderWidth : 10px;
@panelWidth : 100px;
@panelHeight : 100px;
@arrowOffset : 1px;
@arrowBorderWidth : 10px;
@arrowLeft : 20%;
@arrowRight : auto;
@contentPadding : auto;
.flyout-panel{
position: fixed;
border: @borderWidth solid @borderColor;
display: none;
background: @bgColor;
width : @panelWidth;
height: @panelHeight;
.fp-arrow {
position: fixed;
width : 0;
left : @arrowLeft;
right: @arrowRight;
>div{
position: absolute;
border: solid transparent;
height: 0;
width: 0;
bottom: 0;
}
.fa-a1{
border-bottom-color : @borderColor;
border-width : @arrowBorderWidth;
left: 50%;
margin-left: -@arrowBorderWidth;
}
.fa-a2{
border-bottom-color : @bgColor;
border-width : @arrowBorderWidth;
left: 50%;
top: @borderWidth;
margin-left: -@arrowBorderWidth;
}
}
}
附上demo:http://flowerszhong.shop.co/flyout-panel/demo.html
