你是否想知道易企秀炫酷的H5是如何实现的,原理是什么,本文会为你揭秘并还原压缩过的源代码。
易企秀是一款h5页面制作工具,因方便易用成为业界标杆。后续一个项目会用到类似易企秀这样的自定义H5的功能,因此首先分析下易企秀的前端代码,看看他们是怎么实现的,再取其精华去其糟粕。
由于代码较多,且是压缩处理过的,阅读和还原起来较为困难,不过可以先大概分析下原理,然后有针对性的看主要代码,并借助VS Code等工具对变量、函数进行重命名,稍微耐心一点就能大概还原源代码。
分析数据模型
前端分析第一步,看看易企秀的数据模型:
dataList是页面配置信息,elemengts是页面元素的配置信息,obj是H5的配置信息,
加载流程分析
查看H5源代码,发现入口函数是:
eqShow.bootstrap();
顺藤摸瓜,大概分析下,主要流程如下:
主要的功能函数在eqxiu和window对象下面,其中的重点是parsePage、renderPage和app,下面一一来分析。
parsePage
先看主要代码(重命名后的),主要功能是为每一页生成一个section并appendTo(".nr"),另外如果页面有特效,加载相关js库并执行,最后再renderPage。
function parsePage(dataList, response) {
for (var pageIndex = 1; pageIndex <= dataList.length; pageIndex++) {
// 分页容器
$('<section class="main-page"><div class="m-img" id="page' + pageIndex + '"></div></section>').appendTo(".nr");
if (10 == pageMode) {
$("#page" + pageIndex).parent(".main-page").wrap('<div class="flip-mask" id="flip' + pageIndex + '"></div>'),
$(".main-page").css({
width: $(".nr").width() + "px",
height: $(".nr").height() + "px"
});
}
if (dataList.length > 1 && 14 != pageMode && !response.obj.property.forbidHandFlip) {
if (0 == pageMode || 1 == pageMode || 2 == pageMode || 6 == pageMode || 7 == pageMode ||
8 == pageMode || 11 == pageMode || 12 == pageMode || 13 == pageMode || 14 == pageMode) {
$('<section class="u-arrow-bottom"><div class="pre-wrap"><div class="pre-box1"><div class="pre1"></div></div><div class="pre-box2"><div class="pre2"></div></div></div></section>')
.appendTo("#page" + pageIndex)
} else if (3 == pageMode || 4 == pageMode || 5 == pageMode || 9 == pageMode || 10 == pageMode) {
$('<section class="u-arrow-right"><div class="pre-wrap-right"><div class="pre-box3"><div class="pre3"></div></div><div class="pre-box4"><div class="pre4"></div></div></div></section>')
.appendTo("#page" + pageIndex);
}
}
....
renderPage(eqShow, pageIndex, dataList);
// 最后一页
if (pageIndex == dataList.length) {
eqxiu.app($(".nr"), response.obj.pageMode, dataList, response);
addEnabledClassToPageCtrl(response);
}
}
}
hasSymbols || addReportToLastPage(dataList, response);
}
渲染页面和组件
parsePage搭建了页面框架,renderPage实现页面渲染。
rendepage里,核心代码是:
eqShow.templateParser("jsonParser").parse({
def: dataList[pageIndex - 1],
appendTo: "#page" + pageIndex,
mode: "view",
disEvent: disEvent
});
templateParser负责将页面上的elements还原为组件,因此这里核心是要了解下templateParser,大致还原的代码如下:
var jsonTemplateParser = eqShow.templateParser("jsonParser", function () {
function createContainerFunction(container) {
return function (key, value) {
container[key] = value
}
}
function wrapComp(element, mode) {
try {
var comp = components[("" + element.type).charAt(0)](element, mode)
} catch (e) {
return
}
if (comp) {
var elementContainer = $('<li comp-drag comp-rotate class="comp-resize comp-rotate inside" id="inside_' + element.id + '" num="' +
element.num + '" ctype="' + element.type + '"></li>'),
elementType = ("" + element.type).charAt(0);
if ("3" !== elementType && "1" !== elementType) {
elementContainer.attr("comp-resize", "")
}
// 组件类型
/**
* 2 文本
* 3 背景
* 9 音乐
* v video
* 4 图片
* h shape形状
* p 图集
* 5 输入框
* r radio
* c checkbox
* z 多选按钮
* a 评分组件
* b 留言板
* 6 提交按钮
*/
switch (elementType) {
case "p":
elementContainer.removeAttr("comp-rotate");
break;
case "1":
elementContainer.removeAttr("comp-drag");
break;
case "2": // 文本
elementContainer.addClass("wsite-text");
break;
case "3":
// 背景
break;
case "x":
elementContainer.addClass("show-text");
break;
case "4":
// image
element.properties.imgStyle && $(comp).css(element.properties.imgStyle), elementContainer.addClass("wsite-image");
break;
case "n":
elementContainer.addClass("wsite-image");
break;
case "h":
elementContainer.addClass("wsite-shape")
break;
case "5":
elementContainer.removeAttr("comp-input");
break;
case "6":
case "8":
elementContainer.removeAttr("comp-button");
break;
case "v":
elementContainer.removeAttr("comp-video");
elementContainer.addClass("wsite-video");
if (element.properties && element.properties.lock) {
elementContainer.addClass("alock")
}
break;
case "b":
elementContainer.removeAttr("comp-boards");
elementContainer.attr("min-h", 60),
elementContainer.attr("min-w", 230);
break;
default:
break;
}
elementContainer.mouseenter(function () {
$(this).addClass("inside-hover")
}),
elementContainer.mouseleave(function () {
$(this).removeClass("inside-hover")
});
// edit或者非文本type,再套一层
if ("edit" === jsonTemplateParser.mode || "x" !== ("" + element.type).charAt(0)) {
var elementBoxContent = $('<div class="element-box-contents">'),
elementBox = $('<div class="element-box">').append(elementBoxContent.append(comp));
elementContainer.append(elementBox),
"5" !== ("" + element.type).charAt(0) && "6" !== ("" + element.type).charAt(0) && "r" !== element.type && "c" !== element.type && "a" !== element.type && "8" !== element.type && "l" !== element.type && "s" !== element.type && "i" !== element.type && "h" !== element.type && "z" !== element.type || "edit" !== mode || $(comp).before($('<div class="element" style="position: absolute; height: 100%; width: 100%;z-index: 1;">'))
}
// 文本类型,处理font
var k, eleFonts = element.fonts || element.css.fontFamily || element.fontFamily;
if ("2" === elementType || "x" === elementType) {
for (var content = element.content, font_pattern = /font-family:(.*?);/g, matchResults = [], fonts = []; null !== (matchResults = font_pattern.exec(content));)
fonts.push(matchResults[1].trim());
if (1 !== fonts.length || "defaultFont" !== fonts[0] && "moren" !== fonts[0] || (eleFonts = null),
eleFonts) {
if ("view" === jsonTemplateParser.mode && element.css.fontFamily && window.scene && (window.scene.publishTime || !mobilecheck() && !tabletCheck() || (k = "@font-face{font-family:" + element.css.fontFamily + ';src: url("' + element.properties.localFontPath + '") format("truetype");}',