;(function ($, window) { var headIndex = (function () { function headIndex(element, options) { this.settings = $.extend(true, $.fn.headIndex.default, options || {}); this.element = element; this.init(); } headIndex.prototype = { init: function () { this.articleWrap = $(this.settings.articleWrapSelector); this.headerList = this.articleWrap.find(':header'); this.indexBox = $(this.settings.indexBoxSelector); this.indexScrollBox = $(this.settings.indexScrollBoxSelector); this.scrollBody = $(this.settings.scrollSelector); this.scrollWrap = $(this.settings.scrollWrap); this.manual = false; this.mouseHovered = false; if (this.indexBox.length === 0 || this.headerList.length === 0) { return null; } this.initHeader(); this.event(); }, initHeader: function () { for (var i = 0; i < this.headerList.length; i++, this.autoId++) { //文章header添加id和计算高度 this.headerList[i].id = this.headerList[i].id || "header-id-" + this.autoId; this.headerList[i].topHeight = this.offsetTop(this.headerList[i]); this.headerList[i].h = Number(this.headerList[i].tagName.charAt(1)); } this.tempHtml = []; this.buildHtml(this.buildTree()); var res = ''; this.indexBox.html(res); }, event: function () { var that = this; var manualValTimer = null; this.indexBox.on('click.headindex', function (event) { var target = $(event.target); if (target.hasClass(that.settings.linkClass)) { event.preventDefault(); var indexItem = target.parent('.' + that.settings.itemClass); //手动点击的时候,屏蔽滑动响应 that.manual = true; if (manualValTimer) { clearTimeout(manualValTimer); manualValTimer = null; } manualValTimer = setTimeout(function () { that.manual = false; }, 400); that.current(indexItem); //滚动到当前的标题 that.scrollTo(event.target.getAttribute('href')) } }); //鼠标 Hover 监听 this.indexScrollBox.on('mouseover', function (event) { that.mouseHovered = true; }); this.indexScrollBox.on('mouseleave', function (event) { that.mouseHovered = false; }); //滑动监听 $(this.scrollWrap).scroll(function () { if (that.manual) return; that.updateCurrent(); }); //默认选中当前滑动的位置 that.updateCurrent(); }, updateTopHeight: function () { var length = this.headerList.length; var i; if (length === 0) return; //第一个和最后一个标题的高度都没有发生变化,默认整体高度没变 if (this.headerList[0].topHeight === this.offsetTop(this.headerList[0]) && this.headerList[length - 1].topHeight === this.offsetTop(this.headerList[length - 1])) { return; } //第一个和最后一个标题的高度变化的差值相同,默认整体标题的变化差值相同 if ((this.headerList[0].topHeight - this.offsetTop(this.headerList[0])) === (this.headerList[length - 1].topHeight - this.offsetTop(this.headerList[length - 1]))) { var hx = this.offsetTop(this.headerList[0]) - this.headerList[0].topHeight; for (i = 0; i < this.headerList.length; i++, this.autoId++) { this.headerList[i].topHeight += hx; } return; } //其他变化,整体进行重新计算和赋值 for (i = 0; i < this.headerList.length; i++, this.autoId++) { this.headerList[i].topHeight = this.offsetTop(this.headerList[i]); } }, current: function (indexItem) { var subBox, currentClass = 'current'; if (indexItem.length === 0 || indexItem.hasClass(currentClass)) { return; } //移除其他位置的current类 var otherCurrent = this.indexBox.find('li.' + currentClass); if (otherCurrent.length > 0) { otherCurrent.removeClass(currentClass); } //先清除全部的open标记 this.indexBox.find('ul.open').removeClass('open'); //打开当前下级别的subItemBox subBox = indexItem.children('.' + this.settings.subItemBoxClass); if (subBox.length > 0) { subBox.addClass('open').slideDown(); } //为了应对非常快速滑动的时候,scroll函数略过父级的box var parentsBox = indexItem.parents('ul.' + this.settings.subItemBoxClass); if (parentsBox.length > 0) { parentsBox.addClass('open').slideDown() } //关闭其他位置打开的subItemBox 排除当前父级上的subItemBox subBox = this.indexBox.find('ul.' + this.settings.subItemBoxClass).not('.open'); if (subBox.length > 0) { subBox.slideUp() } //为当前添加current类 indexItem.addClass(currentClass); //如果超出了目录的边界,则滚动到目录中 if (this.mouseHovered){ return; } var relativeOffsetTopToWrapper = indexItem.position().top; var indexScrollBoxScrollTop = this.indexScrollBox.scrollTop(); var indexScrollBoxHeight = this.indexScrollBox.height(); if (relativeOffsetTopToWrapper < 20 + indexItem.height()){ var target = indexScrollBoxScrollTop + relativeOffsetTopToWrapper - 20 - indexItem.height(); if (target < 30){ target = 0; } this.indexScrollBox.stop().animate({ scrollTop: target }, 'normal'); } if (relativeOffsetTopToWrapper > indexScrollBoxHeight - 10){ this.indexScrollBox.stop().animate({ scrollTop: indexScrollBoxScrollTop + relativeOffsetTopToWrapper - indexScrollBoxHeight + 10 + indexItem.height() }, 'normal'); } }, buildHtml: function (tree) { if (tree === undefined || tree.length === 0) return; for (var i = 0; i < tree.length; i++) { this.tempHtml.push("
  • " + "" + tree[i].item.innerText + ""); if (tree[i].children.length !== 0) { this.tempHtml.push(""); } this.tempHtml.push("
  • ") } }, buildTree: function () { var current = null, tempCur, indexTree = []; for (var i = 0; i < this.headerList.length; i++) { if (current == null) { current = {item: this.headerList[i], parent: null, children: [],}; indexTree.push(current); continue; } if (current.item.h < this.headerList[i].h) { tempCur = {item: this.headerList[i], parent: current, children: [],}; current.children.push(tempCur); current = tempCur; continue; } if (current.item.h === this.headerList[i].h) { tempCur = {item: this.headerList[i], parent: current.parent, children: [],}; ((current.parent && current.parent.children) || indexTree).push(tempCur); current = tempCur; continue; } while (current != null && current.item.h > this.headerList[i].h) { current = current.parent; } if (current == null) { current = {item: this.headerList[i], parent: null, children: [],}; indexTree.push(current); continue; } i--; } return indexTree; }, search: function (start, end, findValue) { if (this.headerList.length === 0) return null; if (end - start <= 1) { if (this.headerList[end].topHeight < findValue) { return this.headerList[end]; } return this.headerList[start]; } if (start < end) { var middleIndex = parseInt((start + end) / 2); var middleValue = this.headerList[middleIndex].topHeight; if (findValue < middleValue) { end = middleIndex; } else if (findValue > middleValue) { start = middleIndex } else { return this.headerList[middleIndex]; } return this.search(start, end, findValue) } }, /** * 计算每个标题距离文档顶部的垂直高度, * 为了应对不同需求,如果计算不准确,可以修改这个方法 * @param elem * @returns {number} */ /*已重写该方法*/ offsetTop: function (elem) { return $(elem).offset().top - this.settings.offset; /*var wrapTop = this.articleWrap[0].getBoundingClientRect().top var eTop = elem.getBoundingClientRect().top return parseInt(eTop - wrapTop - this.settings.offset)*/ // var rect, win; // if (!elem) { // return; // } // if (!elem.getClientRects().length) { // return {top: 0, left: 0}; // } // // rect = elem.getBoundingClientRect(); // win = elem.ownerDocument.defaultView; // return parseInt(rect.top + win.pageYOffset); }, /** * 滑动到指定id选择器的标题 * @param eid 标题的id值 */ scrollTo: function (eid) { this.scrollBody.stop().animate({ scrollTop: this.offsetTop(document.getElementById(eid.substr(1))) + 8 }, 'normal', 'easeOutExpo'); }, /** * 更新当前位置 */ updateCurrent: function () { var scrollTop = this.scrollBody.scrollTop(); this.updateTopHeight(); var find = this.search(0, this.headerList.length - 1, scrollTop);// if (!find) { return; } var indexItem = this.indexBox .find('a[href="#' + find.id + '"]') .parent('li.' + this.settings.itemClass); this.current(indexItem); }, /** * 清除缓存对象 */ clean: function () { if (this.element) { this.element.data("headIndex", null) } }, /** * 临时忽略滚动监听 */ ignoreScrollEvent: function (ignore) { if (ignore) { this.manual = true; } else { this.manual = false; this.updateCurrent(); } } }; headIndex.prototype.autoId = 1; return headIndex; })(); $.fn.headIndex = function (options) { return this.each(function () { var $this = $(this), instance = $this.data("headIndex"); /*此处为适配 pjax 注释了下面的 if*/ /*if (!instance) {*/ instance = new headIndex($this, options); $this.data("headIndex", instance); /*}*/ if ($.type(options) === "string") return instance[options](); }); }; //----------------------------------- // 默认参数 //----------------------------------- $.fn.headIndex.default = { articleWrapSelector: ".article-wrap",/*包裹文章的选择器*/ indexBoxSelector: ".index-box",/*包裹目录索引的选择器*/ indexScrollBoxSelector: "#leftbar_part2_inner",/*包裹目录索引的选择器 (新增)*/ scrollSelector: 'body,html',/*滑动元素的选择器*/ scrollWrap: window,/*能够监听到scrollSelector滑动的选择器*/ subItemBoxClass: "index-subItem-box", itemClass: "index-item", linkClass: "index-link", offset: 0,/*滑动偏移量 按需求进行偏移*/ } })(jQuery, window);