笔者最近做一个新手引导功能,这个引导独立于实际产品,但是却包含部分产品的功能,是一个单独的模块。
在实现上,一开始想的尽量把引导功能和产品功能分开。做的时候也尽量这样做了。
最外层是引导功能的组件,实现引导相关的逻辑,这个组件包含了产品功能组件,产品功能组件不用不关心引导组件的存在。功能组件只需要实现功能并且提供相应的事件函数就行。
最后为了能在功能组件里高亮显示引导区,直接在引导组件里通过选择器把功能组件里的元素层级改为比较高。这样当引导组件遮挡这个屏幕时,功能区元素是高亮显示且可以点击的。边实际操作产品功能边完成整个引导逻辑。
当笔者做完以后,发现如果要在引导中间加入一个流程,不能通过修改一个配置就完成。需要修改一些代码,相当于需要熟悉整个引导流程当代码才能进行修改和添加。这缺乏一定当扩展性,而且维护成本会比较高。于是笔者在网上寻找其他人是如何做新手引导的。
在网上找了一圈,有对引导功能进行总结的:
显然,我目前的代码是不能满足这个总结的。
如果只是让用户看引导,并且点击引导层,然后一步一步说明。这样做还比较简单,网上的实现方案找着找着就找到张哥那里区了。张哥用border做遮罩,元素本身没背景,所以高亮了引导区。然后可以通过配置定制引导,基本满足了上面的总结。具体代码如下:
CSS代码:
.cover {
display: none;
position: absolute;
width: 0; height: 0;
left: 0; top: 0; right: 0; bottom: 0;
border: 0 solid #000;
opacity: .75;
filter: alpha(opacity=75);
z-index: 9;
/* 过渡效果 */
transition: all .25s;
/* 边缘闪动问题fix */
box-shadow: 0 0 0 100px #000;
overflow: hidden;
}
.cover::before {
content: '';
width: 100%; height:100%;
border-radius: 50%;
border: 400px solid #000;
position: absolute;
left: -400px; top: -400px;
box-shadow: inset 0 0 5px 2px rgba(0,0,0,.75);
}
/* IE7, IE8 img */
.cover > img {
width: 100%; height: 100%;
}
HTML代码:
<div id="cover" class="cover"></div>
JS代码:
var coverGuide = function(cover, target) {
var body = document.body, doc = document.documentElement;
if (cover && target) {
// target size(width/height)
var targetWidth = target.clientWidth,
targetHeight = target.clientHeight;
// page size
var pageHeight = doc.scrollHeight,
pageWidth = doc.scrollWidth;
// offset of target
var offsetTop = target.getBoundingClientRect().top + (body.scrollTop || doc.scrollTop),
offsetLeft = target.getBoundingClientRect().left + (body.scrollLeft || doc.scrollLeft);
// set size and border-width
cover.style.width = targetWidth + 'px';
cover.style.height = targetHeight + 'px';
cover.style.borderWidth =
offsetTop + 'px ' +
(pageWidth - targetWidth - offsetLeft) + 'px ' +
(pageHeight - targetHeight - offsetTop) + 'px ' +
offsetLeft + 'px';
cover.style.display = 'block';
// resize
if (!cover.isResizeBind) {
if (window.addEventListener) {
window.addEventListener('resize', function() {
coverGuide(cover, target);
});
cover.isResizeBind = true;
} else if (window.attachEvent) {
window.attachEvent('onresize', function() {
coverGuide(cover, target);
});
cover.isResizeBind = true;
// IE7, IE8 box-shadow hack
cover.innerHTML = '<img src="guide-shadow.png">';
}
}
}
};
var elCover = document.getElementById('cover');
var arrElTarget = [
document.getElementsByTagName('a')[0],
document.getElementById('backTo'),
document.getElementById('image')
], index = 0;
coverGuide(elCover, arrElTarget[index]);
elCover.onclick = function() {
index++;
if (!arrElTarget[index]) {
index = 0;
}
coverGuide(elCover, arrElTarget[index]);
};
代码还兼容了IE7和IE8,真的很厉害耶。但是这个模式并不适合在引导过程中,是真的在操作功能。但是稍微改一改能不能满足呢?
功能组件如果提供自身被用户操作的各种事件,然后引导功能可以订阅各种事件,且引导的高亮层是可以被点穿的(但是按照上面的模式,高亮层和遮罩是同一个元素,如果高亮层被点穿,遮罩层可能也被点穿)。
还思考了一种方法,就是假设所有的组件都继承引导功能。可以通过外部配置激活这些组件的引导功能。从而使得可以配置引导,而且引导是组件的一部分,继而能获得组件所有的状态和操作组件。这个方法前期组件如果做好了,感觉会很好用。