• 我在 impress.js 中学到的小套路


    我在 impress.js 中学到的小套路

    写在开篇

    作为了一个自学 JavaScript 才一个月的新手,前几天“妄图”研究 jQuery-3.1.0 源码,结果自然是被虐得死去活来。机缘巧合之下,遇到了 impress.js ,代码量只有 jQ 的十分之一,看起来挺好下手,研究了两天,勉强弄懂了其中的原理。于是写下此文,记录我在 impress.js 中学到的小套路。

    附上 impress.js github 链接。

    impress.js 简介

    套路开始

    如何使用 JS 为某个元素添加多个样式?

    一般情况下,在 JS 中改变元素的样式都是通过元素的 style 属性,比如这样:

    element.style.width = 100 + "px";
    

    添加一个样式还好,如果添加多个呢,比如同时添加 width 和 height ,难道要这样?

    element.style.width = 100 + "px";
    element.style.height = 50 + "px";
    

    非也,同时添加多个样式,可以通过函数来解决,如下:

    /**
     * 为指定的元素添加一组 CSS 样式
     * @param  ele    指定的元素
     * @param  props  一组 CSS 属性和值,JSON 的形式,属性名和属性值都要加引号
     * @return        返回指定的元素
     */
    var css = function(ele, props) {
        var key, pkey;
        for (key in props) {
            if (props.hasOwnProperty(key)) {
                pkey = pfx(key);
                if (pkey !== null) {
                    ele.style[pkey] = props[key];
                }
            }
        }
        return ele;
    }
    

    如何使用 JS 为 CSS 属性添加当前浏览器能够识别的前缀?

    比如 transform ,为了让他在各个浏览器上都支持,在 CSS 中就得写成这样:

    #box {
    	        transform: rotateX(45deg);
    	-webkit-transform: rotateX(45deg);
    	   -moz-transform: rotateX(45deg);
    	    -ms-transform: rotateX(45deg);
    	     -o-transform: rotateX(45deg);
    }
    

    可是这样写真的很麻烦啊,怎么办?用函数自动添加!

    /**
     * 在需要的时候,为 CSS 属性添加当前浏览器能够识别的前缀
     * @param  prop  一定要记住,参数是一个字符串,所以传入的 CSS 属性一定要加引号
     * @return       返回当前浏览器能够识别的 CSS 属性
     */
    var pfx = (function() {
        var prefixes = "Moz Webkit O ms".split(" ");
        var style = document.createElement("dummy").style;
        var memory = {};
    
        return function(prop) {
            var uProp = prop.charAt(0).toUpperCase() + prop.slice(1);
            var props = (prop + " " + prefixes.join(uProp + " ") + uProp).split(" ");
    
            memory[prop] = null;
            for (var i in props) {
                if (style[props[i]] !== undefined) {
                    memory[prop] = props[i];
                    break;
                }
            }
            return memory[prop];
        }
    })();
    

    把上面的 css() 和 pfx() 两个函数结合起来,就可以方便地在 JS 中为元素添加样式啦。

    如何通过 hash 值定位元素?

    首先元素必须得有一个 id ,这个简单,遍历一下,没有 id 的加上 id 就可以了,在此不表。

    有了 id 之后,就可以改变 hash 值啦,如下:

    h3[i].onclick = function(event) {
    	window.location.hash = event.target.id;
    }
    

    改变 hash 值有什么用?当然有用,假如 hash 值是 #h3-3 ,就会自动跳转到 id 为 h3-3 的元素上面,然后转念一想,这不是可以生成目录吗?锚点什么的,out 啦!

    基本思想是这样子:首先,保证每一个标题标签都有一个 id ,然后遍历所有的标签,找到标题标签,提取出来,利用自定义列表生成一个目录;目录下的每一项也都有一个 id ,为 # + 对象标题的 id 。当点击目录上的标题时,将这个标题的 id 赋给 hash 值,就 OK 啦。

    附上本文生成目录的 JS 代码,仅供参考!

    window.onload = function() {
    	addId();
    	createContents();
    	createLink();
    }
    
    // 保证所有的标题标签都有 id ,以 h2-* h3-* 的格式
    var addId = function() {
    	var h3 = document.querySelectorAll("h3");
    	for (var i = 0; i < h3.length; i++) {
    		if( !h3[i].id ) {
    			h3[i].id = "h3-" + (i + 1);
    		}
    	}
    	var h2 = document.querySelectorAll("h2");
    	for (var i = 0; i < h2.length; i++) {
    		if( !h2[i].id ) {
    			h2[i].id = "h2-" + (i + 1);
    		}
    	}
    };
    
    // 创建目录
    var createContents = function() {
    	// 创建 dl 标签
    	var dl = document.createElement("dl");
    	dl.id = "dl";
    	document.body.appendChild(dl);
    
    	// 遍历所有的标签,找到 h2 和 h3 
    	var tags = document.getElementsByTagName("*");
    	for (var i = 0; i < tags.length; i++) {
    		// 如果是 h2 ,则将标题保存在 dt 标签中,并给这个 dt 标签一个 id ,格式为 #h2-*	
    		if(tags[i].nodeName.toLowerCase() === "h2") {
    			var dt = document.createElement("dt");
    			dt.id = "#" + tags[i].id;
    			var dt_text = document.createTextNode(tags[i].firstChild.nodeValue);
    			dt.appendChild(dt_text)
    			dl.appendChild(dt);
    		}
    		// 如果是 h3 ,则将标题保存在 dd 标签中,并给这个 dd 标签一个 id ,格式为 #h3-*	
    		if(tags[i].nodeName.toLowerCase() === "h3") {
    			if(tags[i].firstChild) {
    				var dd = document.createElement("dd");
    				dd.id = "#" + tags[i].id;
    				var dd_text = document.createTextNode(tags[i].firstChild.nodeValue);
    				dd.appendChild(dd_text);
    				dl.appendChild(dd);
    			}
    		}
    	}
    }
    
    // 定义目录与标题的联系
    // 点击目录中的某一项,将这一项的 id 赋给 hash 值,通过 hash 值的改变定位到相应的标题
    function createLink() {
    	var dl = document.getElementById("dl");
    	var tags = dl.getElementsByTagName("*");
    	for (var i = 0; i < tags.length; i++) {
    		tags[i].onclick = function() {
    			window.location.hash = this.id;
    		}
    	}
    }
    

    自定义属性 data 与 dataset 对象

    在 impress.js 中,充分利用了 data 来定义各个 step 的样式。首先在 HTML 中的元素标签上定义 data 属性,然后在 JS 中通过 dataset 对象读取,然后 通过 css() 函数为元素添加这些样式。虽然最终的结果与直接在 CSS 样式表中定义的一样,不过这能通过 JS 对样式进行更多控制。

    举个栗子,下面是 HTML :

    第三幕

    然后在 JS 中通过 el.dataset 获取这些属性,长这样:

    var data = el.dataset = {
    	      x : 0,
    	      z : 1000,
    	rotateY : 45,
    	rotateZ : 80,
    	  scale : 1
    }
    

    然后就可以通过 data 获取具体的属性值,就像这样:

    step = {
        translate: {
            x: toNumber( data.x ),
            y: toNumber( data.y ),
            z: toNumber( data.z )
        },
        rotate: {
            x: toNumber( data.rotateX ),
            y: toNumber( data.rotateY ),
            z: toNumber( data.rotateZ || data.rotate )
        },
        scale: toNumber( data.scale, 1 ),
        el: el
    };
    

    然后调用 css() 函数将这些样式同时添加到元素上。

    css( el, {
        position: "absolute",
        transform: "translate(-50%,-50%)" +
                   translate( step.translate ) +
                   rotate( step.rotate ) +
                   scale( step.scale ),
        transformStyle: "preserve-3d"
    } );
    

    鉴于 step.translate 和 step.rotate 是一个对象,为了解析它们的值,还得需要两个辅助函数,如下:

    /**
     * 设置 3D 转换元素的 translate 值
     * @param  t 位移值,以对象字面量的形式,属性值不需要带单位
     * @return   返回 "translate3d() "
     */
    var translate = function(t) {
        return "translate3d(" + t.x + "px," + t.y + "px," + t.z +"px) ";
    }
    
    /**
     * 设置 3D 转换元素的 rotate 值
     * @param  r 旋转值,以对象字面量的形式,属性值不需要带单位
     * @return   返回 "rotateX() rotateY() rotateZ() " 
     */
    var rotate = function( r, revert ) {
        var rX = " rotateX(" + r.x + "deg) ",
            rY = " rotateY(" + r.y + "deg) ",
            rZ = " rotateZ(" + r.z + "deg) ";
    
        return revert ? rZ + rY + rX : rX + rY + rZ;
    };
    

    自定义事件并立即触发

    在 impress.js 的初始化函数中,函数的最后,是这样的:

    triggerEvent( root, "impress:init", { api: roots[ "impress-root-" + rootId ] } );
    

    调用的 triggerEvent() 函数长这样:

    /**
     * 自定义事件并立即触发
     * @param  el        触发事件的元素
     * @param  eventName 事件名
     * @param  detail    事件信息
     */
    var triggerEvent = function( el, eventName, detail ) {
        var event = document.createEvent( "CustomEvent" );
        // 事件冒泡,并且可以取消冒泡
        event.initCustomEvent( eventName, true, true, detail );
        el.dispatchEvent( event );
    };
    

    我个人理解,在初始化完成之后,自定义了一个 impress:init 事件,并立即触发,然后在 root 元素接收该事件,并以此为依据,执行另外的任务。

    同样在 impress.js 中,每一幕的进入在经过一段时间的延迟之后,都会自定义一个 impress:stepenter 事件并触发,在 root 上接收到这个事件,从而改变 hash 值。

  • 相关阅读:
    IDEA复制module
    input输入框限制输入数字(含小数)
    毕设周总结---3
    皮尔森相关系数算法
    毕设周总结---2
    毕设周总结---1
    解释器模式实例
    架构模式——解释器模式
    课后作业---质量属性
    软件架构师如何工作
  • 原文地址:https://www.cnblogs.com/cc156676/p/5837511.html
Copyright © 2020-2023  润新知