简洁的三角箭头面板(flyout-panel.js)【附demo】

最近UI上经常遇到点击一个按钮,出现一个带指向箭头的面板的情况,如图:

简洁的三角箭头面板(flyout-panel.js)【附demo】 

于是就写了个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;
    });    
})(); 

调用方式:

 

_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",
                        }
                    });
                    
                },

 

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('');
}
行内样式方式的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>
外部样式方式则提供一个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;
        
}
    }
}

 附上demo:http://flowerszhong.shop.co/flyout-panel/demo.html

 

上一篇:一个由“有道词典”引起的前端bug


下一篇:免费编程书