重新整理一下阿里前端实习编程题

前文

昨天收到阿里实习的编程测验通知,本来打算再缓缓,然而今天下午睡醒了忍不住就点了进去…然后发现今年的和去年的不同,去年是有选择题什么的,今年这个就直接一道综合题,一开始看的有点蒙,题目是:用js实现一个弹出层,需要水平垂直居中,要有半透明遮罩层效果。 因为以前开发项目的时候写过类似的组件,然后我改了改扔上去。。。最后发现我写的是css版本的,一脸懵逼。而且水平垂直居中的方式并不是很好。不过我认为每一次失误都是一次很好学习的机会,因此今天好好整理一下加深印象。

先来谈谈水平垂直居中

单行文本

从简单的来入手,单行文本水平居中只要让line-height 值等于 height值。水平居中的话加上text-align:center即可


<div class="vertical">23333</div>

.vertical {
  height: 100px;
  line-height: 100px;
  text-align:center;
}


多行内容居中

只要给div设置上下相同的padding就行了


<div class="item">
	tes
	<br />
	123
	124
</div>
                          
.item {padding-top:30px;padding-bottom:30px;text-align:center}

绝对定位 absolute + 定位高度 top + margin-top 法

这也是我们最常用的方法。这种方法主要是针对多行元素来进行元素的垂直居中,而并非是此元素的内容垂直居中。

给容器设置绝对定位position:absolute

定位高度(top:50%)和margin-top:-height/2

元素进行绝对定位后脱离文档流,其宽度是根据元素内容所占宽度来计算的,为这能让其视觉效果更好,最好给元素定义一个宽度值.

如果需要指定容器宽度,为了让它能够水平居中,我们还需给一个宽度width,并且加上margin-left:-width/2


<div class="vertical">23333</div>

.vertical {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 460px;
    height: 200px;
    margin-top: -100px;
    margin-left: -230px;
    text-align: center;
}


imgn

table法

这个方法是经常见的。

div模拟表格效果,外部div设置display为table,内部div设置display为table-cell,并且将vertical-align设为middle

但是这样只做到了垂直居中,水平居中需要外度div设置宽度,可以是100%,然后内部添加text-align: center;


<div id="container">
    <div id="content">content</div>
</div>

#container {
    height: 200px;
    display: table;    
}
#content {
    display:table-cell;  
    vertical-align: middle;
    text-align:center;
}	


imgn

再来谈谈主要是css构成的弹窗层

先来看我这个不成熟的代码


<!doctype html>
<html>
<style>
#overlay {
    display: none;
}

.overlay-mask {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: 10;
    cursor: pointer;
    background: #B3B3B3;
}

.overlay-inner {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 460px;
    height: 200px;
    margin-top: -100px;
    margin-left: -230px;
    text-align: center;
    z-index: 11;
    background-color: red;
}

.header {
    background-color: blue;
    height: 20px;
    width: 100%;
}

.content {
    background-color: yellow;
    height: 140px;
}

.footer {
    background-color: green;
    height: 40px;
}
</style>

<head>
</head>

<body>
    <button id="open">打开</button>
    <div id="overlay">
        <div class="overlay-mask"></div>
        <div class="overlay-inner">
            <div class="header">
                标题
            </div>
            <div class="content">
                内容
            </div>
            <div class="footer">
                <button id="ok">确定</button>
                <button id="cancel">取消</button>
            </div>
        </div>
    </div>
</body>
<script type="text/javascript">
var btnOpen = document.getElementById("open");
var btnClose = document.getElementById("cancel");
var ok = document.getElementById("ok");
var modal = document.getElementById("overlay");

btnOpen.onclick = function() {
    modal.style.display = "block";
}

btnClose.onclick = function() {
    modal.style.display = "none";
}

ok.onclick = function() {
    modal.style.display = "none";
}
</script>

</html>


写的那么凌乱主要是一开始有点慌,那个倒计时时间瞄一下就没多少了0-0还是类似体验比较少,以后习惯就好了…

主要是通过定义一个overlay来管理弹出层,overlay-mask是遮罩,overlay-inner是内部内容,通过上面的绝对定位加margin值修改达到垂直水平居中,再通过定位堆叠上下文来展示。

事件绑定用了最基础的onclik,但是这其实不怎么好。因此这份只能作为思路参考。

imgn

js版本

原始版本

我从网上找个了最原始的版本:

  <div><input type="button" value="查看效果" id="bt"/></div> 
  <div id="win" style="display:none;"></div>
  

function show(){ 
  var win=document.getElementById("win"); 
  win.style.display="block"; 
  win.style.position="absolute"; 
  win.style.top="50%"; 
  win.style.left="50%"; 
  win.style.marginTop="-75px"; 
  win.style.marginLeft="-150px"; 
  win.style.background="cyan"; 
  win.style.width="300px"; 
  win.style.height="200px"; 
  win.style.zIndex="1000";
  var mark = document.createElement("div"); 
  mark.setAttribute("id","mark"); 
  mark.style.background="#000"; 
  mark.style.width="100%"; 
  mark.style.height="100%"; 
  mark.style.position="absolute"; 
  mark.style.top="0"; 
  mark.style.left="0"; 
  mark.style.zIndex="500"; 
  mark.style.opacity="0.3"; 
  mark.style.filter="Alpha(opacity=30)"; 
  document.body.appendChild(mark); 
  document.body.style.overflow = "hidden"; 
} 
window.onload=function(){
  var obt=document.getElementById("bt");
  obt.onclick=function(){
    show();
  }
}

具体思路是一样的,只不过用原生Js将css封装了起来。而且这份代码有个小缺点就是你如果调整整个窗体的大小宽度,它就不再垂直居中对齐。一般大家会想到监听resize事件,然后动态居中,但是这里又引发一个问题,就是resize事件触发的很频繁,不停调用resize事件其实事件非常消耗性能的事情。这里就需要用到事件节流,这里直接给出代码:


   var throttle = function(fn, delay){
        var timer = null;
        return function(){
            var context = this, args = arguments;
            clearTimeout(timer);
            timer = setTimeout(function(){
                fn.apply(context, args);
            }, delay);
        };
     };

    window.addEventListener("resize", throttle(resize,100));
    

再优化一点就是记得缓存DOM查找节点。

正文才刚刚开始

之前都是餐前点心,现在我们需要深入一下。我们需要制作一个可配置的弹出层插件,参考经典的artDialog,我们把需求列一下


支持链式调用

支持设置以下参数:
1.width
2.height
3.弹出层标题title,有title自定义添加x符号
4.弹出层内容content
5.添加Ok事件自动添加Ok按钮
6.添加cancel事件自动添加cancel按钮

首先按照我的思考是先把div这些内容处理了


    <div id="Xoverlay">
        <div class="overlay-mask"></div>
        <div class="overlay-inner">
            <div class="overlay-header">
                标题
                <div id="overlay-close">X</div>
            </div>
            <div class="overlay-content">
                内容
            </div>
            <div class="overlay-footer">
                <button id="overlay-ok">确定</button>
                <button id="overlay-cancel">取消</button>
            </div>
        </div>
    </div>
  

注意,因为将整个弹出层已经分类出来,因此配置中也要加上。


 Xoverlay.prototype.defaults = {
            'target': null,
            'width': 460,
            'height': 270,
            'title': null,
            'content': null,
            'ok': null,
            'cancel': null
 }

这里我把关键的地方说一下,具体代码在文章底部。

我这里先构造了一个函数



    function Xoverlay(options) {
        //载入配置

        this.options = this.extend(this.defaults, options)

        //初始化
        this.init()
    }

这里的extend代码


    Xoverlay.prototype.extend = function(dest, source) {
        for (var prop in source) {
            // hasOwnProperty 是为了防止source的proto被遍历处理。
            // hasOwnProperty用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在
            if (source.hasOwnProperty(prop)) {
                dest[prop] = source[prop]
            }
        }
        return dest
    }

这里我们为什么要extend呢,主要是为了把传入的参数和默认选择更新替换。主要这里的hasOwnProperty 一开始我也没想到做这样的处理,这里做这样的判断主要是防止source里的proto可能会被for in遍历出来。

init()函数主要做了三件事情,首先根据传入参数动态生成布局。然后判断传入的参数有没有target,如果有,把点击事件绑定到该target上面,如果没有绑定到body上。

然后给存在的按钮添加事件。这里注意我们传入的参数是这样的:


var Xoverlay = new Xoverlay({
	 target:'#test',
		title:"6666",
		ok:function(){console.log('233')},
		cancel:function(){console.log("cancel");return false},
		content:"This is Content2"
	}).setWidth(460).setHeight(400)
	

这里的return false可以阻止默认关闭事件。我们在绑定事件的时候注意一下判断即可


                if (that.options.cancel() != false) {
                    overlay.style.display = "none"
                }


为了兼容我引入了跨浏览器事件兼容处理。具体效果如下:

imgn

这里贴出第一版本的代码,这份代码还有点缺陷。比如我还没考虑到多个实例同时存在绑定的情况,比如点击按钮再弹窗。遮罩层还不是可配置的。事实上这个还能再细化出来不少需求。后续需求我将更新在github,有兴趣的可以关注。

 
 /*
  Xoverlay 弹出层插件
  2017-3-3 练手作品
  By Linshuizhongying
 */
;
(function(window) {
    'use strict'

    // 跨浏览器事件兼容处理
    var EventUtil = {

        addHandler: function(element, type, handler) {
            if (element.addEventListener) {
                element.addEventListener(type, handler, false)
            } else if (element.attachEvent) {
                element.attachEvent("on" + type, handler)
            } else {
                element["on" + type] = handler
            }

        },
        removeHandler: function(element, type, handler) {
            if (element.removeEventListener) {
                element.removeEventListener(type, handler, false)
            } else if (element.detachEvent) {
                element.detachEvent("on" + type, handler)
            } else {
                element["on" + type] = null
            }

        }

    }


    Xoverlay.prototype.extend = function(dest, source) {
        for (var prop in source) {
            // hasOwnProperty 是为了防止source的proto被遍历处理。
            // hasOwnProperty用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在
            if (source.hasOwnProperty(prop)) {
                dest[prop] = source[prop]
            }
        }
        return dest
    }


    function Xoverlay(options) {
        //载入配置

        this.options = this.extend(this.defaults, options)

        //初始化
        this.init()
    }
    // 默认配置
    Xoverlay.prototype.defaults = {
            'target': null,
            'width': 460,
            'height': 270,
            'title': null,
            'content': null,
            'ok': null,
            'cancel': null
        }
        //初始函数

    Xoverlay.prototype.init = function() {
        // 根据需求拼接字符串
        var layout = this.template()

        //如果有指定元素挂在指定元素上,不然则直接挂在body上

        if (this.options.target) {
            var target = document.querySelector(this.options.target)
            document.body.appendChild(layout)
            EventUtil.addHandler(target, "click", function() {
                document.getElementById("Xoverlay").style.display = "block"
            })

        } else {
            document.body.appendChild(layout)
            EventUtil.addHandler(document.body, "click", function(e) {

                document.getElementById("Xoverlay").style.display = "block"

            })
        }
        this.bindEvents();
    }
    Xoverlay.prototype.template = function() {
        var _title =
            '            <div class="overlay-header">' + this.options.title + '                <div id="overlay-close">X</div>' + '            </div>'
            // 如果指定了title那么页面加进去
        var title = this.options.title == null ? " " : _title

        var _content =
            '            <div class="overlay-content">' + this.options.content + '            </div>'
            // 如果指定了content那么加进去
        var content = this.options.content == null ? " " : _content

        var _ok = this.options.ok == null ? " " : '<button id="overlay-ok">确定</button>'
        var _cancel = this.options.cancel == null ? " " : '<button id="overlay-cancel">取消</button>'


        var footer = this.options.cancel && this.options.ok == null ? " " :
            '            <div class="overlay-footer">' + _ok + _cancel + '            </div>'

        var xoverlay = document.createElement('div')
        xoverlay.setAttribute("id", "Xoverlay")

        xoverlay.innerHTML =
            '        <div class="overlay-mask"></div>' + '        <div class="overlay-inner">' + title + content + footer + '        </div>'

        return xoverlay
    }


    Xoverlay.prototype.setWidth = function(number) {
        var width = document.getElementsByClassName('overlay-inner')[0]
        width.style.width = number + 'px'
        width.style.marginLeft = -number / 2 + 'px'
        return this
    }

    Xoverlay.prototype.setHeight = function(number) {
        var height = document.getElementsByClassName('overlay-inner')[0]
        height.style.height = number + 'px'
        height.style.marginTop = -number / 2 + 'px'
        return this
    }


    Xoverlay.prototype.bindEvents = function() {
        var overlay = document.getElementById("Xoverlay");
        var close = document.getElementById("overlay-close")
        var ok = document.getElementById("overlay-ok")
        var cancel = document.getElementById("overlay-cancel")
        var that = this
        if (this.options.title) {
            EventUtil.addHandler(close, "click", function(e) {
                e.preventDefault()
                e.stopPropagation()
                overlay.style.display = "none"

            })
        }
        if (this.options.ok) {
            EventUtil.addHandler(ok, "click", function(e) {
                e.preventDefault()
                e.stopPropagation()
                if (that.options.ok() != false) {
                    overlay.style.display = "none"
                }


            })
        }
        if (this.options.cancel) {
            EventUtil.addHandler(cancel, "click", function(e) {
                e.preventDefault()
                e.stopPropagation()
                    // 如果代码里有return false 那么阻止消失
                if (that.options.cancel() != false) {
                    overlay.style.display = "none"
                }

            })
        }
    }

    window.Xoverlay = Xoverlay
}(window));

 
Table of Contents