多组处理, 仅展开一个区块的折叠效果(原生Js和jQ版)

上午在项目中遇到的一个问题, 折腾了半小时才把思路理顺. 需求是, 同一个页面, 有多组(不固定), 每组区块数量不一定一样的小区块. 要求每次只展开一个区块. 实现原理其实很简单, 点击导航, 若它的区块为隐藏, 则展开它, 同时, 隐藏掉同组其他区块; 若它的区块为展开, 则隐藏它, 同时, 展开同组其他区块中的一个. 一开始以为仅仅简单的两个遍历就能搞定. 但事实并非如此. 冷静思考了下, 通过点击的元素取到当前组的相关元素, 再单独处理当前组才合理. 顺着这个思路, 功能终于实现了, 写了原生JS版本, 用同样的思路写了个jQ版本. 时间关系, 写的也比较零散, 就没有封装. 其实, 对这种思路也不是很满意, 感觉太散了, 哪位大师有更好的思路请赐教.

另, 在演示页面中, 点击jQ版区块的标题, 会有一个JS错误, 那是因为获取JS版下的h2时, 我偷了个懒, 把jQ的也遍历进去了. 我想, 实际应用中, 也不会有人同一个效果, 一边用JS一边用jQ吧.
核心代码点此查看样例

//原生JS版本 ***** start
window.onload=function(){
	//共用函数区
	var iBase={
		//document.getElementById
		Id: function(name){return document.getElementById(name)},
		//通过class获取元素
		GetByClass: function(name,tagName,elem){
			var c=[];
			var re=new RegExp('(^|\\s)'+name+'(|\\s$)');
			var e=(elem || document).getElementsByTagName(tagName || '*');
			for(var i=0; i < e.length; i++){
				if(re.test(e[i].className)){
					c.push(e[i]);
				}
			}
			return c;
		},
		//获取样式属性
		AttrStyle: function(elem,attr){
			if(elem.attr){
				return elem.style[attr];
			}else if(elem.currentStyle){
				return elem.currentStyle[attr];
			}else if(document.defaultView && document.defaultView.getComputedStyle){
				attr=attr.replace(/([A-Z])/g,'-$1').toLowerCase();
				return document.defaultView.getComputedStyle(elem,null).getPropertyValue(attr);
			}else{
				return null;
			}
		},
		//获取祖辈元素中符合指定样式的元素
		Parents: function(elem,name){
			var r=new RegExp('(^|\\s)'+name+'(|\\s$)');
			elem=elem.parentNode;
			if(elem!=null){
				return r.test(elem.className) ? elem : iBase.Parent(elem,name) || null;
			}
		},
		//取索引值
		Index: function(cur,obj){
			for(var i=0; i < obj.length; i++){
				if(obj[i]==cur){
					return i;
				}
			}
		}

	}

	//变量定义
	var listBox=iBase.GetByClass('js','div');
	var navItem=iBase.Id('demo').getElementsByTagName('h2');//此处将jQ区块中的h2也取到了,所以页面会有个小小的错误
	var icoItem=null,boxItem=null,boxDisplay=null,elemIndex=null,elemParent=null;
	//初始化展开第一个
	for(var i=0; i < listBox.length;i++){
		iBase.GetByClass('box','div',listBox[i])[0].style.display='block';
		listBox[i].getElementsByTagName('span')[0].innerHTML='-';
	}
	//遍历所有点击项
	for(var i=0; i < navItem.length;i++){
		navItem[i].onclick=function(){
			elemParent=iBase.Parents(this,'js');//获取当前点击所在区块
			navItem=elemParent.getElementsByTagName('h2');//获取当前区块下的点击项
			icoItem=elemParent.getElementsByTagName('span');//获取当前区块下的展开关闭
			boxItem=iBase.GetByClass('box','div',elemParent);//获取需要控制的区块
			elemIndex=iBase.Index(this,navItem);//获取当前点击在当前区块点击项中的索引
			//切换展开关闭图标
			icoItem[elemIndex].innerHTML= icoItem[elemIndex].innerHTML=='-' ? '+' : '-';
			if(iBase.AttrStyle(boxItem[elemIndex],'display')=='block'){
				//控制项展开状态下,隐藏当前,展开其他的第一项
				//此处有个展开0/1的判断,因为当点击第一个时是不能再展开第一个的
				boxItem[elemIndex].style.display='none';
				if(elemIndex==0){
					boxItem[1].style.display='block';
					icoItem[1].innerHTML='-'
				}else{
					boxItem[0].style.display='block'
					icoItem[0].innerHTML='-'
				}
			}else{
				//控制项展开状态下,展开当前,隐藏其他项
				boxItem[elemIndex].style.display='block';
				for(var k=0;k < boxItem.length; k++){
					if(k!=elemIndex){
						boxItem[k].style.display='none';
						icoItem[k].innerHTML='+';
					}
				}
			}
		}
	}

}

//jQuery版本 ***** start
$(function(){
	//变量定义区
	var _listBox=$('.jq');
	var _navItem=$('.jq>h2');
	var _boxItem=null, _icoItem=null, _parents=null, _index=null;

	//初始化第一个展开
	_listBox.each(function(i){
		$(this).find('div.box').eq(0).show();
		$(this).find('h2>span').eq(0).text('-');
	});

	//遍历所有的点击项
	_navItem.each(function(i){
		$(this).click(function(){
			//找到当前点击父元素为listbox(单个区块)的元素
			_parents=$(this).parents('.listbox');
			_navItem=_parents.find('h2');//此区块中的点击项
			_icoItem=_parents.find('span');//此区块中的展开关闭图标
			_boxItem=_parents.find('div.box');//此区块中展开关闭项
			_index=_navItem.index(this);//取得当前点击在当前区块下点击项中的索引值
			if(_boxItem.eq(_index).is(':visible')){
				//若当前点击项下的展开关闭项是显示的,则关闭,同时展开另外项中的第一个
				_boxItem.eq(_index).hide().end().not(':eq('+_index+')').first().show();
				_icoItem.eq(_index).text('+').end().not(':eq('+_index+')').first().text('-');
			}else{
				//若当前点击项下的展开关闭项是隐藏的,则展开,同时隐藏其他项
				_boxItem.eq(_index).show().end().not(':eq('+_index+')').hide();
				_icoItem.eq(_index).text('-').end().not(':eq('+_index+')').text('+');
			}
		});
	});
});

HTML技巧: 语义化你的代码

最近在读阿当的《Web前端开发修炼之道》, 其中有不少东西值得前端路上的朋友学习. 结合自己日常编码的一些经验, 我将陆陆续续将一些从书中学到的以及自己总结的一些前端方面的技巧分享给大家.

HTML语义化, 似乎是一个老生常谈的问题. Google一下,也有大把关于语义化的文章. 为什么要语义化标签? 我是这样认为的:HTML的每个标签都有它特定的意义,而语义化,就是让我们在适当的位置用适当的标签, 以更好的让人和机器(机器可理解为浏览器可理解为搜索引擎)都一目了然. 如果我的解释不够明了, 请Google.

如何在合适的位置使用合适的标签?
这是一个简单的理解逻辑. 比如, h1~h6标签是用于标题类的; ul是用于无序列表的; ol是用于有充列表的; dl是用于定义列表的; em,strong标签是用来强调的…说白了, HTML标签的每个英文释义决定了它的语义(本文后面, 我会放一份常用HTML标签的英文释义对照表供参考).
什么是让人和机器都能一目了然?
检查HTML页面是否语义化最好的方法, 便是去掉页面的CSS链接, 看网页结构是否井然有序, 页面是否仍然有很好的可读性. 为什么可以这么说? 大家都知道浏览器都有默认的样式(推荐使用Chrome的Web Developer Tools for Chrome插件, 或者Firefox的Web Developer插件), 比如h1~h6, 会有加粗/字号依次减小、上下边距的默认样式, ul、ol、dl都有默认的项目符号, strong默认有加粗的样式…所以, 同样的页面, 语义化良好的HTML可以在页面CSS去掉的情况下依然有良好的表现.
还有一点, 好的语义化编码, 对搜索引擎有更好的友好性. 搜索蜘蛛是不认识你的CSS的, 但它能识别HTML标签.
下面是一个简单的例子:
未语义化语义化后的效果差异






通过上面的简单的示例和没有任何CSS定义情况下的效果图, 该明白了两者的区别了吧. 如果你在学习HTML5, 它的header、footer、sidebar、article等元素都是新增的语义化标签.
HTML编码语义化是迈向高质量前端开发的一步. 即更好的遵循Web标准, 也能让你页面在去掉样式后依然井然有序. 关于语义化更多更详细的介绍, 可以自行Google或阅读阿当的《Web前端开发修炼之道》第三章.
附: 标签语义中英文对照表(删除线为html5不支持的标签)

标签名 英文全拼 中文翻译
a anchor
abbr abbreviation 缩写词
acronym acronym 取首字母的缩写词
address address 地址
b bold 粗体
big big 变大
blockquote block quotation 区块引用于
br break 换行
caption caption 标题
center center 居中
dd definition description 定义描述
del delete 删除
div division 分隔
dl definition list 定义列表
dt definition term 定义术语
em emphasized 加重
fieldset fieldset 域集
font font 字体
h1~h6 header1~header6 标题1~标题6
hr horizontal rule 水平尺
i italic 斜体
ins inserted 插入
legend legend 图标
li list item 列表项目
ol ordered list 排序列表
p paragraph 段落
pre preformatted 预定义格式
s strikethrough 删除线
small small 变小
span span 范围
strong strong 加重
sub subscripted 下表
sup superscripted 上标
u underlined 下划线
ul unordered list 不排序列表
var variable 变量

原生JS实现按数据源均分时间点幻灯效果(已封装)

腾讯新闻详情页有一个事件进展效果, 觉得挺有意思. 于是, 就有了本文的效果: 按数据源均分时间点幻灯. 花了三个多小时写的, 当然, 包括样式与调试. 兼容主流, 建议在Chrom,Firefox,Opera,Safari等标准浏览器中查看. IE下没有阴影及圆角.
实现了根据源数据(样例中是一个JSON数据组)总条数, 均分出时间点以平滑向右动画方式显示在时间线上, 当鼠标划过时间点时, 显示对应的日期及标题. 鼠标划过事件, 充分考虑了用户体验, 当用户快速(无意识移动)从时间点上划过时, 并不触发相应事件.
相关的方法说明及使用, 请参见下面的注释或发评论, 同时也欢迎大家找bug并提交.
原生JS实现按数据源均分时间点幻灯效果
JS核心代码点此查看样例

var JSONData=[{...},{...},...];//数据源,一切皆因它而生,因它而灭

function iTimePoint(iTimeSlideId, dateId, timeLineId, titleTop, titleId, defaultShow){
    /* 传入参数说明:
     * iTimeSlideId: 外围ID名. 本样例DOM中#itimeslide;
     * dateId: 日期ID名. 本样例DOM中#date;
     * timeLineId: 时间点分布ID名. 本样例DOM中#timeline;
     * titleTop: 标题容器上方小三角ID名. 本样例DOM中#titletop;
     * titleId: 标题容器ID名. 本样例DOM中#title;
     * defaultShow: 设定初始显示的时间点, 默认为0, 可不传值
     */

    //参数判断,测试用,成功运行后可删除
    if (arguments.length < 5 || arguments.length>6) {
        alert('参数传入错误,请传入5或6个值! :)');
        return false;
    }

	//通用方法
    var iBase = {
        //document.getElementById
        Id: function(name){
            return document.getElementById(name);
        },
        //时间点动画显示
        PointSlide: function(elem, val){
            //可通过修改i+=5中的5控制滑动速度
            for (var i = 0; i <= 100; i += 5) {
                (function(){
                    //这个pos定义很重要,若直接使用闭包获取到的不是上面的i
                    var pos = i;
                    //平滑移动
                    setTimeout(function(){
                        elem.style.left = pos * val / 100 + 'px';
                    }, (pos + 1) * 10);
                })();
            }
        },
        //为元素添加样式
        AddClass: function(elem, val){
            //若元素无class,直接赋值
            if (!elem.className) {
                elem.className = val;
            }else {
                //否则通过添加空格新增一个class
                var oVal = elem.className;
                oVal += ' ';
                oVal += val;
                elem.className = val;
            }
        },
        //获取元素索引
        Index: function(cur, obj){
            for (var i = 0; i < obj.length; i++) {
                if (obj[i] == cur) {
                    return i;
                }
            }
        }
    }
	//整个函数变量定义区
    var dataLen = JSONData.length;
    var iTimeSilde = iBase.Id(iTimeSlideId);
    var date = iBase.Id(dateId);
    var timeLine = iBase.Id(timeLineId);
    var titletop = iBase.Id(titleTop);
    var title = iBase.Id(titleId);
    var iTimeSildeW = iTimeSilde.offsetWidth;//幻灯区实际宽度
    var timePoint = document.createElement('ul');//用来存储时间点的ul
    var timePointLeft = null;//时间点相对于父元素左边距离
    var timePointLeftCur = null;//每两个时间点间距
    var pointIndex = 0;//时间点在队列中的索引值
	var defaultShow = defaultShow || 0;//默认显示的时间
	var clearFun=null;//当用户无意识的划过时中止执行
	var that=null;
    //根据数据条数生成对应的时间点html
    for (var i = 0; i < dataLen; i++) {
        timePoint.innerHTML += '<li></li>';
    }
    //将时间点插入到时间线DIV中
    timeLine.appendChild(timePoint)
    var timePoints = timeLine.getElementsByTagName('li');
    //时间点平滑显示
    for (var i = 0; i < timePoints.length; i++) {
		//每两个时间点间间距
        timePointLeftCur = parseInt(iTimeSildeW / (dataLen + 1));
		//计算对应时间点左边距
        timePointLeft = (i + 1) * timePointLeftCur;
		//时间点动画形式初始化
        iBase.PointSlide(timePoints[i], timePointLeft);
		//初始显示时间点
        setTimeout(function(){
            timePoints[defaultShow].onmouseover();
        }, 1200);
		//获取时间点默认class值,为鼠标事件做准备
        timePoints[i].oldClassName = timePoints[i].className;
        timePoints[i].onmouseover = function(){
			that = this;//确保clearFun中的this是当前的this
			//提升用户体验,当用户无意识地划过时不执行函数
			clearFun=setTimeout(function(){
				//计算出当前时间点索引值,为鼠标划出做准备
		        pointIndex = iBase.Index(that, timePoints);
				//去除上一个时间点高亮样式
				for (var m = 0; m < timePoints.length; m++) {
					if (m != pointIndex) {
						timePoints[m].className = timePoints[m].oldClassName
					}
				}
				//为当前时间点加载高亮样式
	            iBase.AddClass(that, 'hover');
				//切换日期及标题值
	            date.innerHTML = '<span>' + (JSONData[pointIndex]['date'] || '') + '</span>';
	            title.innerHTML = '<a href="' + (JSONData[pointIndex]['href'] || '') + '">' + (JSONData[pointIndex]['title'] || '') + '</a>';
	            //改变日期及标题的位置,此处减去的数字,可根据实际样式调整
	            date.style.left = ((pointIndex + 1) * timePointLeftCur - 25) + 'px';
	            titletop.style.left = ((pointIndex + 1) * timePointLeftCur + 6) + 'px';
	            //当标题框左边距与标题框宽度之和大于外围宽度时,以右边为绝对点
	            if ((title.offsetWidth + (pointIndex + 1) * timePointLeftCur) < iTimeSildeW) {
	                title.style.left = ((pointIndex + 1) * timePointLeftCur - timePointLeftCur) + 'px';
	            }else {
	                title.style.left = (iTimeSildeW - title.offsetWidth) + 'px';
	            }
	            //显示日期/时间点/标题
	            date.style.display = 'block';
	            titletop.style.display = 'block';
	            title.style.display = 'block';
			},200);//200为认定无意识划过的时间,可自行调节
        }
        timePoints[i].onmouseout = function(){
			//若停留时间低于200ms,认定为无意识划过,中止函数
			clearTimeout(clearFun);
        }
    }
}

为WordPress更换SynTaxHighlighter注意事项

一直琢磨着讲博客原来的coolcode高亮插件换成SyntaxHighlighter高亮, 心动不如行动, 终于花了几小时时间完成了这项琐碎的工作.
SyntaxHighlighter的实用与强大, 就不用我说了. 其实coolcode也是款很不错的高亮插件, 但书写时相对繁琐一些.
关于安装这个插件, Google一下, 会有文章介绍, 我所尝试并有效的方法只有直接引入JS及CSS文件. 一开始我也是直接下了个zip包, 直接添加新插件, 失败. 打开包一看, 里面根本没有相关的Pph配置文件. 还以为下错了文件. 然后在ZH CEXO童鞋的提醒下, 看了下其中的test页面, 原来它是纯JS+CSS实现的. 好吧, 麻烦一点, 直接加载文件吧. 不要为test文档中大片的JS调用吓倒, 除了一个核心shCore.js, 其他的全根据自己实际使用情况调用.有一点, 千万别忘了在外链文件后, 执行一下SyntaxHighlighter.all(). 文章最后我会贴上我所使用的CSS及JS.
本地测试, 新的语法高亮成功. 接下来就是将以前的高亮方式替换成新的. 一篇篇文章修改? 当然可以, 但是会死人的. 最好的方法当然是直接批量替换数据库. 安全起见, 先用phpMyAdmin导出(我的是虚拟主机, SQL Front连不上, 很郁闷~)文章表(我的是i_posts), 然后在本地用SQL Front执行批量更新替换(也可以直接在phpMyAdmin中执行). 下面是我使用的SQL方法:

update i_posts set post_content=REPLACE(post_content,'coolcode lang="javascript" linenum="off"','pre class="brush: js;"');

注意,上面的语句一定要根据实际仔细写完整, 否则替换错了是不能撤消的..
但数据库也不是万能的, 批量替换只能替换相同的部分, 有些地方写的时候难免会换个顺序, 多个空格之类的, 所以, 还是得一页页的翻翻, 把不正确的更正过来.
所有数据更新完毕, 本地测试无误, 导入. 一个新的语法高亮就完成了.
使用方法, 暂不详述, 请参考插件包中的演示文档或前往SyntaxHighlighter高亮查看.
最后, 如果你在本站浏览时发现比较奇怪的代码错位或者其他bug, 那极有可能是数据替换时出的岔子, 请及时转告我, 先谢过~
下面是我的调用的CSS及JS文件(PHP的gzip压缩方法,请参考: http://mrthink.net/ue-php-gzip-function/):
CSS.php

  include('syntax_css/shCore.css');//核心CSS
  include('syntax_css/shThemeDefault.css');//模板CSS

JS.php

  include('syntax_js/shCore.js');//核心文件
  include('syntax_js/shBrushJScript.js');//JS高亮
  include('syntax_js/shBrushCSS.js');//CSS高亮
  include('syntax_js/shBrushPHP.js');//PHP高亮
  include('syntax_js/shBrushXml.js');//HTML类(含xml)高亮

最后的JS

SyntaxHighlighter.config.clipboardSwf = 'syntax_js/clipboard.swf';//这个是鼠标划过复制小图标的flash
SyntaxHighlighter.all();

原生JS获取元素样式属性值的方法

实际应用中, 获取元素样式在实际应用中一定常用到, 若是纯粹html中, 直接elem.style.attr就可获取, 但更多的时候我们是要从CSS中获取元素的最终样式属性.所以, 我们得利用IE的currentStyle和W3C的getPropertyValue获取.
elem.style.attr获取样式的方法就不说了. 先来看currentStyle方法, 此对象ie专属, 代表了在全局样式表、内嵌样式和 HTML 标签属性中指定的对象格式和样式. IE下通过它, 就可以获取元素的CSS属性值.
而针对其他标准浏览器, W3C也提供了一个方法getPropertyValue, 此方法, 稍有点复杂, 首先要通过document.defaultView.getComputedStyle获得CSS的样式对象, 然后通过该对象的getPropertyValue获取属性值.
上述两种方法, 无论IE还是W3C的, 有一点是相同的, 它们所获取的是元素CSS属性最终值. 这一点与CSS的优先级是相同的.
有一点不同的是, IE的方法是通过CSS属性的驼峰式名(如textAlign)获取, 而W3C的方法是通过元素CSS原来的属性名(如text-align)获取的, 所以, 在使用W3C方法时, 需要对CSS属性名做个简单的处理.
基于此, 我们可以封装一个获取元素属性值的方法, 如下:

function attrStyle(elem,attr){
	if(elem.style[attr]){
		//若样式存在于html中,优先获取
		return elem.style[attr];
	}else if(elem.currentStyle){
		//IE下获取CSS属性最终样式(同于CSS优先级)
		return elem.currentStyle[attr];
	}else if(document.defaultView && document.defaultView.getComputedStyle){
		//W3C标准方法获取CSS属性最终样式(同于CSS优先级)
		//注意,此法属性原格式(text-align)获取的,故要转换一下
		attr=attr.replace(/([A-Z])/g,'-$1').toLowerCase();
		//获取样式对象并获取属性值
		return document.defaultView.getComputedStyle(elem,null).getPropertyValue(attr);
	}else{
		return null;
	}
}

记得精通JavaScript一书中获取元素位置那一节(忘了是第6还是第7章), 有获取元素样式属性值更详细的解释. 第一次知道document.defaultVies.getComputedStyle就是从这本书来的. 很棒的一本书, 有兴趣的朋友话一定要看看.


渔夫科技