博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
易企秀前端压缩源码分析与还原
阅读量:5955 次
发布时间:2019-06-19

本文共 46092 字,大约阅读时间需要 153 分钟。

你是否想知道易企秀炫酷的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++) {            // 分页容器            $('
').appendTo(".nr"); if (10 == pageMode) { $("#page" + pageIndex).parent(".main-page").wrap('
'), $(".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) { $('
') .appendTo("#page" + pageIndex) } else if (3 == pageMode || 4 == pageMode || 5 == pageMode || 9 == pageMode || 10 == pageMode) { $('
') .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 = $('
  • '), 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 = $('
    '), elementBox = $('
    ').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($('
    ')) } // 文本类型,处理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");}', b(k))), "object" == typeof eleFonts && eleFonts.constructor === Object) { if (!jQuery.isEmptyObject(eleFonts)) for (var q in eleFonts) u[q] || ("edit" === jsonTemplateParser.mode ? k = "@font-face{font-family:" + q + ";src: url(" + PREFIX_FILE_HOST + eleFonts[q] + ") format(woff);}" : window.scene && window.scene.publishTime && (k = "@font-face{font-family:" + q + ';src: url("' + PREFIX_S2_URL + "fc/" + q + "_" + element.sceneId + "_" + scene.publishTime + '.woff") format("woff");}'), b(k), u[q] = !0) } else u[eleFonts] || ("edit" === jsonTemplateParser.mode ? k = "@font-face{font-family:" + eleFonts + ";src: url(" + PREFIX_FILE_HOST + element.preWoffPath + ") format(woff);}" : window.scene && window.scene.publishTime && (k = "@font-face{font-family:" + eleFonts + ';src: url("' + PREFIX_S2_URL + "fc/" + eleFonts + "_" + element.sceneId + "_" + scene.publishTime + '.woff") format("woff");}'), b(k), u[eleFonts] = !0); "edit" === jsonTemplateParser.mode && localStorage.setItem("shoppingFontFamily", JSON.stringify(u)) } } // 处理css if (element.css) { var elementWidth = 320 - parseInt(element.css.left, 10); elementContainer.css({ width: elementWidth }); elementContainer.css({ width: element.css.width, height: element.css.height, left: element.css.left, top: element.css.top, zIndex: element.css.zIndex, bottom: element.css.bottom, transform: element.css.transform }); if (0 === element.css.boxShadowSize || "" + element.css.boxShadowSize == "0") { element.css.boxShadow = "0px 0px 0px rgba(0,0,0,0.5)"; if ("edit" !== jsonTemplateParser.mode && "x" === ("" + element.type).charAt(0)) { return elementContainer.append(comp), elementContainer.find(".element-box").css({ borderStyle: element.css.borderStyle, borderWidth: element.css.borderWidth, borderColor: element.css.borderColor, borderTopLeftRadius: element.css.borderTopLeftRadius, borderTopRightRadius: element.css.borderTopRightRadius, borderBottomRightRadius: element.css.borderBottomRightRadius, borderBottomLeftRadius: element.css.borderBottomLeftRadius, boxShadow: element.css.boxShadow, backgroundColor: element.css.backgroundColor, opacity: element.css.opacity, width: "100%", height: "100%", overflow: "hidden" }), elementContainer.find("img").css({ width: "100%" }), elementContainer; } } // Android 微信,图片,设置borderColor isAndroid() && isWeixin() && "" + element.type == "4" && "0px" !== element.css.borderRadius && 0 === element.css.borderWidth && element.properties.anim && (element.css.borderWidth = 1, element.css.borderColor = "rgba(0,0,0,0)"); var elementCss = $.extend(!0, {}, element.css); delete elementCss.fontFamily, elementBox.css(elementCss).css({ width: "100%", height: "100%", transform: "none" }), elementBox.children(".element-box-contents").css({ width: "100%", height: "100%" }), // 设置宽高 "4" !== ("" + element.type).charAt(0) && "n" !== ("" + element.type).charAt(0) && "p" !== ("" + element.type).charAt(0) && "h" !== ("" + element.type).charAt(0) && "t" !== ("" + element.type).charAt(0) && "z" !== ("" + element.type).charAt(0) && $(comp).css({ width: element.css.width, height: element.css.height }), // w01 w02 设置lineHeight ("w01" === element.type || "w02" === element.type) && $(comp).css({ lineHeight: element.css.height + "px" }), // shape 类型 "h" === ("" + element.type).charAt(0) && ($(comp).find("g").length ? $(comp).find("g").attr("fill", element.css.color) : $(comp).children().attr("fill", element.css.color), elementBox.children(".element-box-contents").css("position", "relative")) } return elementContainer } } /** * 将element按zindex排序 */ function sortElementsByZindex(elements) { for (var i = 0; i < elements.length - 1; i++) for (var j = i + 1; j < elements.length; j++) if (parseInt(elements[i].css.zIndex, 10) > parseInt(elements[j].css.zIndex, 10)) { var element = elements[i]; elements[i] = elements[j], elements[j] = element } for (var e = 0; e < elements.length; e++) elements[e].css.zIndex = e + 1 + ""; return elements } function parseElements(pageDef, $edit_wrapper, mode) { $edit_wrapper = $edit_wrapper.find(".edit_area"); var i, elements = pageDef.elements; if (elements) for (elements = sortElementsByZindex(elements), i = 0; i < elements.length; i++) if (elements[i].sceneId = pageDef.sceneId, "" + elements[i].type == "3") { // type == 3 var component = components[("" + elements[i].type).charAt(0)](elements[i], $edit_wrapper); // if is edit mode, dispatch edit event "edit" === mode && editEvents[("" + elements[i].type).charAt(0)] && editEvents[("" + elements[i].type).charAt(0)](component, elements[i]) } else { var comp = wrapComp(elements[i], mode); if (!comp) continue; $edit_wrapper.append(comp); // invoke interceptors for (var n = 0; n < interceptors.length; n++) interceptors[n](comp, elements[i], mode); afterRenderEvents[("" + elements[i].type).charAt(0)] && ( afterRenderEvents[("" + elements[i].type).charAt(0)](comp, elements[i]), "edit" !== mode && ( parseElementTrigger(comp, elements[i]), r(comp, elements[i]) ) ), "edit" === mode && editEvents[("" + elements[i].type).charAt(0)] && editEvents[("" + elements[i].type).charAt(0)](comp, elements[i]) } } function getEventHandlers() { return editEvents } function getComponents() { return components } function addInterceptor(interceptor) { interceptors.push(interceptor) } function getInterceptors() { return interceptors } var components = {}, editEvents = {}, afterRenderEvents = {}, interceptors = [], _width = containerWidth = 320, _height = containerHeight = 486, p = 1, s = 1, parser = { getComponents: getComponents, getEventHandlers: getEventHandlers, addComponent: createContainerFunction(components), bindEditEvent: createContainerFunction(editEvents), bindAfterRenderEvent: createContainerFunction(afterRenderEvents), addInterceptor: addInterceptor, getInterceptors: getInterceptors, wrapComp: wrapComp, disEvent: !1, mode: "view", parse: function (parseInfo) { var edit_wrapper = $('
      '), mode = this.mode = parseInfo.mode; // page 定义 this.def = parseInfo.def, parseInfo.disEvent && (this.disEvent = !0), "view" === mode && tplCount++; // 页面容器 var pageContainer = $(parseInfo.appendTo); return containerWidth = pageContainer.width(), containerHeight = pageContainer.height(), p = _width / containerWidth, s = _height / containerHeight, parseElements(parseInfo.def, edit_wrapper.appendTo($(parseInfo.appendTo)), mode) } }; return parser });

      上面的重点是parseElements,先把elements按zindex排序,然后逐个渲染。

      注意,渲染是根据elementType,从components找到对应的组件,然后创建一个实例,因此这里要单独说下组件是如何定义的。

      先看下一个组件的配置信息大概是这样,有id,css,type和动画等配置信息:

      {    "id": 29,    "css": {        "top": 124.93546211843,        "left": 62.967731059217,        "color": "#676767",        "width": 195,        "height": 195,        "zIndex": "1",        "opacity": 1,        "boxShadow": "0px 0px 0px rgba(0,0,0,0.5)",        "transform": "rotateZ(45deg)",        "lineHeight": 1,        "paddingTop": 0,        "borderColor": "rgba(255,255,255,1)",        "borderStyle": "double",        "borderWidth": 4,        "borderRadius": "0px",        "boxShadowSize": 0,        "paddingBottom": 0,        "backgroundColor": "rgba(252,230,238,0.16)",        "borderRadiusPerc": 0,        "boxShadowDirection": 0,        "textAlign": "left",        "borderBottomRightRadius": "0px",        "borderBottomLeftRadius": "0px",        "borderTopRightRadius": "0px",        "borderTopLeftRadius": "0px"    },    "type": "2",    "pageId": "24642",    "content": "
      ", "sceneId": 8831293, "properties": { "anim": { "type": 4, "delay": 0.6, "countNum": 1, "duration": 1, "direction": 0 }, "width": 195, "height": 195 }}

      jsonParser里用一个components对象存储组件,通过addComponent添加组件,key就是组件的type:

      addComponent: createContainerFunction(components)function createContainerFunction(container) {                return function (key, value) {                    container[key] = value                }            }

      添加组件时,type 作为key,value为创建组件的函数:

      // 添加组件1    jsonTemplateParser.addComponent("1", function (element, mode) {        var comp = document.createElement("div");        if (comp.id = element.id,            comp.setAttribute("class", "element comp_title"),            // 设置组件content            element.content && (comp.textContent = element.content),            element.css) {            var item, elementCss = element.css;            for (item in elementCss)                comp.style[item] = elementCss[item]        }        if (element.properties.labels)            for (var labels = element.properties.labels, f = 0; f < labels.length; f++)                $('')                .appendTo($(comp))                .html(labels[f].title)                .css(labels[f].color)                .css("width", 100 / labels.length + "%");        return comp    });

      这样渲染组件时,根据element的类型就能找到createCompFunction,从而创建组件。

      组件动画播放

      H5之所以炫酷,很大一部分因为每个组件都有定制好的CSS3动画,我们这里来看看这些动画是如何执行的。

      代码还是上一部分的代码,我们注意到组件渲染后,有一段代码;

      // invoke interceptors    for (var n = 0; n < interceptors.length; n++)        interceptors[n](comp, elements[i], mode);

      执行interceptors,这个interceptors可以通过addInterceptor注册拦截器,在组件渲染完成后会调用定义的拦截器,组件的动画就是这样来调用的。

      // 添加拦截器执行动画        jsonTemplateParser.addInterceptor(function (comp, element, mode) {            eqxCommon.animation(comp, element, mode, jsonTemplateParser.def.properties)        });

      我们发现,eqxiu通过addInterceptor注册了一个拦截器,该拦截器调用eqxCommon.animation执行组件动画,因此分析eqxCommon.animation就可以了解动画是如何实现的。

      还是先看element里的定义:

      "properties": {            "anim": {                "type": 4,                "delay": 0.6,                "countNum": 1,                "duration": 1,                "direction": 0            },

      我们看到,anim里定义了type,delay,duration等配置信息,可以设想播放动画无非就是解析这个配置,然后执行,其中type应该是对应的各种动画类型,分析代码吧,下面给出破解后的代码:

      // 动画播放序号        var animIndex = 0;        // 处理动画属性        if (element.properties && element.properties.anim) {            var anim = [];            element.properties.anim.length ? anim = element.properties.anim : anim.push(element.properties.anim);            var elementBox = $(".element-box", comp);            elementBox.attr("element-anim", "");            // 找出animations            for (var animType, animTypes = [], anims = [], index = 0, animLength = anim.length; animLength > index; index++)                if (null != anim[index].type &&                    -1 != anim[index].type) {                    animType = eqxCommon.convertType(anim[index]),                        animTypes.push(animType),                        anims.push(anim[index]);                }            if (properties && properties.scale)                return;            // 动画播放类型            element.properties.anim.trigger ?                comp.click(function () {                    // 点击播放                    playAnimation(elementBox, animType, element.properties.anim)                }) :                properties && properties.longPage ?                playAnimation(elementBox, animTypes, anims, !0, element.css) // longpage                :                playAnimation(elementBox, animTypes, anims)        }

      上面的逻辑是先从element里找到anim,放入数组,然后再playAnimation。这里使用了convertType函数将数字type转换为真实的动画类型:

      var convertType = function (a) {        var animType, c, d = a.type;        return "typer" === d && (animType = "typer"),            0 === d && (animType = "fadeIn"),            1 === d && (c = a.direction,                0 === c && (animType = "fadeInLeft"),                1 === c && (animType = "fadeInDown"),                2 === c && (animType = "fadeInRight"),                3 === c && (animType = "fadeInUp")),            6 === d && (animType = "wobble"),            5 === d && (animType = "rubberBand"),            7 === d && (animType = "rotateIn"),            8 === d && (animType = "flip"),            9 === d && (animType = "swing"),            2 === d && (c = a.direction,                0 === c && (animType = "bounceInLeft"),                1 === c && (animType = "bounceInDown"),                2 === c && (animType = "bounceInRight"),                3 === c && (animType = "bounceInUp")),            3 === d && (animType = "bounceIn"),            4 === d && (animType = "zoomIn"),            10 === d && (animType = "fadeOut"),            11 === d && (animType = "flipOutY"),            12 === d && (animType = "rollIn"),            13 === d && (animType = "lightSpeedIn"),            14 === d && (animType = "bounceOut"),            15 === d && (animType = "rollOut"),            16 === d && (animType = "lightSpeedOut"),            17 === d && (c = a.direction,                0 === c && (animType = "fadeOutRight"),                1 === c && (animType = "fadeOutDown"),                2 === c && (animType = "fadeOutLeft"),                3 === c && (animType = "fadeOutUp")),            18 === d && (animType = "zoomOut"),            19 === d && (c = a.direction,                0 === c && (animType = "bounceOutRight"),                1 === c && (animType = "bounceOutDown"),                2 === c && (animType = "bounceOutLeft"),                3 === c && (animType = "bounceOutUp")),            20 === d && (animType = "flipInY"),            21 === d && (animType = "tada"),            22 === d && (animType = "jello"),            23 === d && (animType = "flash"),            26 === d && (animType = "twisterInDown"),            27 === d && (animType = "puffIn"),            28 === d && (animType = "puffOut"),            29 === d && (animType = "slideDown"),            30 === d && (animType = "slideUp"),            24 === d && (animType = "flipInX"),            25 === d && (animType = "flipOutX"),            31 === d && (animType = "twisterInUp"),            32 == d && (animType = "vanishOut"),            33 == d && (animType = "vanishIn"),            animType    };

      播放动画函数在playAnimation里:

      • 大概是先判断类型是否是typer,typer的话是打字机特效,调用相关代码
      • 设置element的的css animation属性,本质上是CSS3的animation动画,可以参见(http://www.w3school.com.cn/css3/css3_animation.asp)
      elementBox.css("animation", "");                elementBox.css("animation", animTypes[animIndex] + " " + anims[animIndex].duration + "s ease " + anims[animIndex].delay + "s " +                    (anims[animIndex].countNum ? anims[animIndex].countNum : ""));                     anims[animIndex].count && animIndex == anims.length - 1 && elementBox.css("animation-iteration-count", "infinite");                    elementBox.css("animation-fill-mode", "both");

      最后,如果有多个动画,在播放完成后继续播放下一个:

      // 动画播放结束,播放下一个动画(一个组件可能有多个动画)                elementBox.one("webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend", function () {                    animIndex++;                    playAnimation(elementBox, animTypes, anims);                })

      页面切换

      由于是多页应用,因此涉及到页面切换,并且页面切换时还需要有对应的切换动画,改工作是由一个eqxiu对象来管理和实现的。

      老套路,先看这块的配置吧,页面的配置在obj下面,其中pageMode定义了翻页效果:

      "obj": {    "id": 8831293,    "name": "房产广告",    "createUser": "1",    "type": 103,    "pageMode": 4,    "image": {},    "property": "{\"triggerLoop\":true,\"slideNumber\":true,\"autoFlipTime\":4,\"shareDes\":\"\",\"eqAdType\":1,\"hideEqAd\":false,\"autoFlip\":true,\"lastPageId\":604964}",    "timeout": "",    "timeout_url": "",    "accessCode": null,    "cover": "syspic/pageimg/yq0KA1UrbkOAV_yiAAFuhyGx9LE397.jpg",    "bgAudio": "{\"url\":\"syspic/mp3/yq0KA1RHT3iAMXYOAAgPq1MjV9M930.mp3\",\"type\":\"3\"}",    "isTpl": 0,    "isPromotion": 0,    "status": 1,    "openLimit": 0,    "startDate": null,    "endDate": null,    "updateTime": 1426045746000,    "createTime": 1426572693000,    "publishTime": 1426572693000,    "applyTemplate": 0,    "applyPromotion": 0,    "sourceId": null,    "code": "U903078B74Q5",    "description": "房产广告",    "sort": 0,    "pageCount": 0,    "dataCount": 0,    "showCount": 44,    "eqcode": "",    "userLoginName": null,    "userName": null},

      pagemode是这样定义的:

      pagemodes = [{            id: 0,            name: "上下翻页"        }, {            id: 4,            name: "左右翻页"        }, {            id: 1,            name: "上下惯性翻页"        }, {            id: 3,            name: "左右惯性翻页"        }, {            id: 11,            name: "上下连续翻页"        }, {            id: 5,            name: "左右连续翻页"        }, {            id: 6,            name: "立体翻页"        }, {            id: 7,            name: "卡片翻页"        }, {            id: 8,            name: "放大翻页"        }, {            id: 9,            name: "交换翻页"        }, {            id: 10,            name: "翻书翻页"        }, {            id: 12,            name: "掉落翻页"        }, {            id: 13,            name: "淡入翻页"        }];

      在renderpage结束后,调用eqxiu.app:

      // 最后一页                if (pageIndex == dataList.length) {                    eqxiu.app($(".nr"), response.obj.pageMode, dataList, response);                    addEnabledClassToPageCtrl(response);                }

      来分析eqxiu.app代码,通过pagemode,我们可以看出翻页打开分为上下翻页、左右翻页两个大类:

      if ("8" == pageMode || "9" == pageMode) {            transformTime = 0.7;            timeoutDelay = 800;        }        // 上下翻页  上下惯性翻页 立体翻页 卡片翻页 放大翻页 上下连续翻页 上下连续翻页        if (0 == pageMode || (1 == pageMode || (2 == pageMode || (6 == pageMode || (7 == pageMode || (8 == pageMode || (11 == pageMode || 12 == pageMode))))))) {            /** @type {boolean} */            upDownMode = true;        } else {            // 左右惯性翻页 左右翻页 左右连续翻页  翻书翻页            if (3 == pageMode || (4 == pageMode || (5 == pageMode || 10 == pageMode))) {                /** @type {boolean} */                leftRightMode = true;            }        }

      然后配置里有一个autoFlip,代表是否自动翻页,通过setInterval设置定时翻页任务:

      // 自动翻页        if (response.obj.property.autoFlip) {            // 自动翻页时间            autoFlipTimeMS = 1000 * response.obj.property.autoFlipTime;            setAndStartAutoFlip(autoFlipTimeMS);        }            /**     * 设置翻页时间间隔并启动翻页     * @param {number} textStatus     * @return {undefined}     */    function setAndStartAutoFlip(autoFlipTime) {        autoFlipTime = autoFlipTime;        pauseAutoFlip();        startAutoFlip();    }                  /**     * 启动自动翻页     * @return {undefined}     */    function startAutoFlip() {        // 通过setInterval        autoFlipIntervalId = setInterval(function () {            if (!(10 === self._scrollMode)) {                if (!isTouching) {                    nextPage();                }            }        }, autoFlipTimeMS);    }

      默认情况下H5是支持touch滑动翻页的,这种滑动操作一般是监听相关事件,开始滑动、滑动中和滑动结束,为了同时支持移动端和PC端,还需要加上鼠标点击事件:

      var isTouch = false;        self._$app.on("mousedown touchstart", function (e) {            if (!self._isforbidHandFlip) {                onTouchStart(e);                isTouch = true;            }        }).on("mousemove touchmove", function (e) {            if (!self._isforbidHandFlip) {                if (isTouch) {                    onTouchMove(e);                }            }        }).on("mouseup touchend mouseleave", function (events) {            if (!self._isforbidHandFlip) {                onTouchEnd(events);                /** @type {boolean} */                isTouch = false;            }        });

      翻页的核心无非就是判断位移是否超过特定的值,比如左右翻页X位移是否大于Y位移并且X的偏移量大于20。因此onTouchStart开始时,记录初始位置,onTouchMove时计算offset变化,按照pageMode执行对应的动画,onTouchEnd时判断位移是否足够,足够就切换页面,否则复位。

      /**         * 开始滑动         * @param {Object} e         * @return {undefined}         */        onTouchStart = function (e) {            /** @type {boolean} */            fa = false;            if (isMobile) {                if (e) {                    e = event;                }            }            if (!self._isDisableFlipPage) {                self.$currentPage = self._$pages.filter(".z-current").get(0);                if (!C) {                    /** @type {null} */                    self.$activePage = null;                }                if (self.$currentPage) {                    if (completeEffect($(self.$currentPage))) {                        isTouching = true;                        isCursorAtEnd = false;                        ignoreEvent = true;                        offsetX = 0;                        offsetY = 0;                        if (e && "mousedown" == e.type) {                            currentPageX = e.pageX;                            currentPageY = e.pageY;                        } else if (e && "touchstart" == e.type) {                            currentPageX = e.touches ? e.touches[0].pageX : e.originalEvent.touches[0].pageX;                            currentPageY = e.touches ? e.touches[0].pageY : e.originalEvent.touches[0].pageY;                        }                        self.$currentPage.classList.add("z-move");                        setAttribute(self.$currentPage.style, "Transition", "none");                        if ("12" == self._scrollMode) {                            /** @type {number} */                            self.$currentPage.style.zIndex = 3;                        }                    }                }            }        };        /**         * 滑动处理         * @param {Object} e         * @return {undefined}         */        onTouchMove = function (e) {            if (isMobile) {                if (e) {                    e = event;                }            }            if (isTouching) {                if (self._$pages.length > 1) {                    if (e && "mousemove" == e.type) {                        offsetX = e.pageX - currentPageX;                        offsetY = e.pageY - currentPageY;                    } else {                        if (e) {                            if ("touchmove" == e.type) {                                offsetX = (e.touches ? e.touches[0].pageX : e.originalEvent.touches[0].pageX) - currentPageX;                                offsetY = (e.touches ? e.touches[0].pageY : e.originalEvent.touches[0].pageY) - currentPageY;                            }                        }                    }                    if (!fa) {                        if (Math.abs(offsetX) > 20 || Math.abs(offsetY) > 20) {                            /** @type {boolean} */                            fa = true;                        }                    }                    switch (self._scrollMode + "") {                        case "0":                        case "1":                        case "2":                        case "15":                            //上下翻页                            upDownFlip();                            break;                        case "3":                        case "4":                            // 左右翻页                            leftRightFlip();                            break;                        case "5":                            // 左右连续翻页                            leftRightLoopFlip();                            break;                        case "7":                            cardFlip();                            break;                        case "8":                            scaleUpFlip();                            break;                        case "9":                            switchFlip();                            break;                        case "11":                            //上下连续翻页                            upDownContinuousFlip();                            break;                        case "12":                            //掉落翻页                            dropFlip();                            break;                        case "13":                        case "14":                            //淡入翻页                            fadeFlip();                            break;                        default:                            break;                    }                }            }        };        /**         *  滑动结束         * @param {?} e         * @return {undefined}         */        onTouchEnd = function (e) {            if (isTouching && completeEffect($(self.$currentPage))) {                isTouching = false;                if (self.$activePage) {                    self._isDisableFlipPage = true;                    var ease;                    ease = "6" == self._scrollMode || "7" == self._scrollMode ? "cubic-bezier(0,0,0.99,1)" : "12" == self._scrollMode ? "cubic-bezier(.17,.67,.87,.13)" : "linear";                    self.$currentPage.style.webkitTransition = "-webkit-transform " + transformTime + "s " + ease;                    self.$activePage.style.webkitTransition = "-webkit-transform " + transformTime + "s " + ease;                    self.$currentPage.style.mozTransition = "-moz-transform " + transformTime + "s " + ease;                    self.$activePage.style.mozTransition = "-moz-transform " + transformTime + "s " + ease;                    self.$currentPage.style.transition = "transform " + transformTime + "s " + ease;                    self.$activePage.style.transition = "transform " + transformTime + "s " + ease;                    // 完成翻页                    if ("0" == self._scrollMode || ("2" == self._scrollMode || ("1" == self._scrollMode || "15" == self._scrollMode))) {                        endUpDownFlip();                    } else if ("4" == self._scrollMode || "3" == self._scrollMode) {                        // 左右翻页                        endLeftRightFlip();                    } else if ("5" == self._scrollMode) {                        //左右连续翻页                        endLeftRightContinueFlip();                    } else if ("6" == self._scrollMode) {                        //立体翻页                        endCubeFlip();                    } else if ("7" == self._scrollMode) {                        //卡片翻页                        endCardFlip();                    } else if ("8" == self._scrollMode) {                        //放大翻页                        endScaleUpFlip();                    } else if ("9" == self._scrollMode) {                        //交换翻页                        endSwitchFlip();                    } else if ("11" == self._scrollMode) {                        //上下连续翻页                        endUpDownContinueFlip();                    } else if ("12" == self._scrollMode) {                        //掉落翻页                        endDropFlip();                    } else if ("13" == self._scrollMode || "14" == self._scrollMode) {                        //淡入翻页                        endFadeFlip();                    }                     /** @type {number} */                    var pageIndex = $(self.$activePage).find(".m-img").attr("id").replace("page", "") - 1;                    if (self._pageData[pageIndex].properties) {                        if (self._pageData[pageIndex].properties.longPage) {                            $(document).trigger("clearTouchPos");                        }                    }                    $(self.$activePage).find("li.comp-resize").each(function (dataAndEvents) {                        /** @type {number} */                        var i = 0;                        for (; i < self._pageData[pageIndex].elements.length; i++) {                            if (self._pageData[pageIndex].elements[i].id == parseInt($(this).attr("id").substring(7), 10)) {                                eqxCommon.animation($(this), self._pageData[pageIndex].elements[i], "view", self._pageData[pageIndex].properties);                                var r20 = getComp(self._pageData[pageIndex].elements[i].id);                                eqxCommon.bindTrigger(r20, self._pageData[pageIndex].elements[i]);                            }                        }                    });                    /** @type {number} */                    var i = 0;                    for (; i < self._pageData.length; i++) {                        if (self._pageData[i].effObj) {                            /** @type {boolean} */                            self._pageData[i].effObj.pause = true;                        }                    }                    if (self._pageData[pageIndex].effObj) {                        self._pageData[pageIndex].effObj.startPlay();                    }                    eqShow.setPageHis(self._pageData[pageIndex].id);                } else {                    self.$currentPage.classList.remove("z-move");                }            }            C = false;        };

      然后再来看自动翻页nextPage

      /**     * 启动自动翻页     * @return {undefined}     */    function startAutoFlip() {        // 通过setInterval        autoFlipIntervalId = setInterval(function () {            if (!(10 === self._scrollMode)) {                if (!isTouching) {                    nextPage();                }            }        }, autoFlipTimeMS);    }

      自动翻页比较简单,模拟滑动操作,当位移足够时就可以自动翻页了:

      /**     *  上一页     * @param {number} direction     * @return {undefined}     */    function prePage(direction) {        if (!(leftRightMode && 2 == direction || upDownMode && 1 == direction)) {            if ("10" != self._scrollMode) {                var offset = 0;                // 开启滑动                onTouchStart();                // 定时器,增加offset,模拟滑动                var poll = setInterval(function () {                    offset += 2;                    if ("0" == self._scrollMode || ("1" == self._scrollMode || ("2" == self._scrollMode || ("6" == self._scrollMode || ("7" == self._scrollMode || ("8" == self._scrollMode || ("11" == self._scrollMode || ("12" == self._scrollMode || ("13" == self._scrollMode || ("14" == self._scrollMode || "15" == self._scrollMode)))))))))) {                        // 纵向翻页,增加y                        offsetY = offset;                    } else {                        if ("3" == self._scrollMode || ("4" == self._scrollMode || ("5" == self._scrollMode || "9" == self._scrollMode))) {                            // 横向翻页,增加x                            offsetX = offset;                        }                    }                    // 触发move操作,模拟滑动                    onTouchMove();                    if (offset >= 21) {                        // 位移超过20,                        clearInterval(poll);                        // 停止滑动,完成翻页                        onTouchEnd();                    }                }, 1);            } else {                // 翻书                $(document).trigger("bookFlipPre");            }        }    }    /**     * 下一页,逻辑和prePage类似     * @param {number} direction     * @return {undefined}     */    function nextPage(direction) {        if (!(leftRightMode && 2 == direction || upDownMode && 1 == direction)) {            if ("10" != self._scrollMode) {                u = false;                var offset = 0;                if ("block" == $("body .boards-panel").css("display")) {                    $("body .boards-panel").hide();                    $("body .z-current").show();                }                onTouchStart();                var poll = setInterval(function () {                    offset -= 2;                    if ("0" == self._scrollMode || ("1" == self._scrollMode || ("2" == self._scrollMode || ("6" == self._scrollMode || ("7" == self._scrollMode || ("8" == self._scrollMode || ("11" == self._scrollMode || ("12" == self._scrollMode || ("13" == self._scrollMode || ("14" == self._scrollMode || "15" == self._scrollMode)))))))))) {                        offsetY = offset;                    } else {                        if ("3" == self._scrollMode || ("4" == self._scrollMode || ("5" == self._scrollMode || "9" == self._scrollMode))) {                            offsetX = offset;                        }                    }                    onTouchMove();                    if (-21 >= offset) {                        clearInterval(poll);                        onTouchEnd();                        if (!triggerLoop) {                            if (!self.$activePage) {                                clearInterval(autoFlipIntervalId);                            }                        }                    }                }, 1);            } else {                $(document).trigger("bookFlipNext");            }        }    }

      总结

      上面是花了大概一天多的时间阅读代码的成果,总结经验就是阅读代码先分析大的流程,再层层递进分析一些细节,就能一步一步接近真相。

      另外,阅读压缩过的代码,可以借助VS Code,善用F2重命名,修改的越多,越接近本来的代码:)


      作者:Jadepeng

      出处:jqpeng的技术记事本--
      您的支持是对博主最大的鼓励,感谢您的认真阅读。
      本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

      你可能感兴趣的文章
      ElasticSearch Client详解
      查看>>
      新零售讲堂之时代下的传统零售业,何去何从?
      查看>>
      c++读取和写入TXT文件的整理
      查看>>
      深入动态人脸识别小场景应用,2019年或将迎来爆发期
      查看>>
      Ionic2 下处理 Android 设备下返回按钮的事件
      查看>>
      linux安全问答(1)
      查看>>
      zabbix监控进程的CPU和内存占用量
      查看>>
      Error creating bean with name 'userServiceImpl': Injection of autowired dependencies failed
      查看>>
      mybatis update返回值的意义
      查看>>
      SVNQuery–如何创建更新索引并查询
      查看>>
      java高质量图片压缩
      查看>>
      源码解读Linux的limits.conf文件
      查看>>
      java this关键字的使用
      查看>>
      linux基础1
      查看>>
      jquery之统计数字parseFloat
      查看>>
      20161121
      查看>>
      Nginx服务器开启gzip压缩功能额必要性
      查看>>
      ZeroMQ研究与应用分析
      查看>>
      Linux基础命令--grep/find
      查看>>
      NTFS(Windows)、ext4(RHEL6)和xfs(RHEL7)文件系统的误删除恢复和备份
      查看>>