重新整理一下阿里前端实习编程题
前文
昨天收到阿里实习的编程测验通知,本来打算再缓缓,然而今天下午睡醒了忍不住就点了进去…然后发现今年的和去年的不同,去年是有选择题什么的,今年这个就直接一道综合题,一开始看的有点蒙,题目是:用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;
}
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;
}
再来谈谈主要是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,但是这其实不怎么好。因此这份只能作为思路参考。
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"
}
为了兼容我引入了跨浏览器事件兼容处理。具体效果如下:
这里贴出第一版本的代码,这份代码还有点缺陷。比如我还没考虑到多个实例同时存在绑定的情况,比如点击按钮再弹窗。遮罩层还不是可配置的。事实上这个还能再细化出来不少需求。后续需求我将更新在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));