動態側邊導航欄 JS 實現

MagicQ123 8年前發布 | 42K 次閱讀 JavaScript開發 JavaScript

背景

來美麗說后做的第一個大的頁面改版就是商家后臺整個發寶頁改版,感覺其中一個比較有意思的地方就是側邊導航欄的實現。頁面內容非常多,涉及到商品的不同屬性信息以及其他一些功能模塊,所以整個頁面的 js 實現也分模塊組織。這次要記錄的側邊導航就是 sideNav.js 這個模塊。

頁面效果

最終的效果如下圖(如果有美麗說商家賬號也可以直接訪問該 鏈接 來看):

頁面根據商品的不同屬性信息劃分為:在店鋪的分類、款式/屬性、標題/副標題/簡述、貨號/美麗制造貨號、SKU設置、價格/庫存、運費、圖片信息和其他信息共8個大模塊。其中圖片信息模塊下又分為封面圖、產品介紹、商品細節、商品實拍、尺碼說明、資質認證和店鋪介紹工7個固定模塊,以及可以動態添加或刪除的自定義模塊。現在側邊導航要實現的功能為:

  1. 點擊側邊導航欄的不同選項,頁面滾動到對應的功能區域。
  2. 根據頁面當前滾動位置,高亮所在區域對應的側邊導航項;如果該項不在視野內,則滾動導航欄使該項移動至視野內。
  3. 點擊添加或刪除自定義模塊后,能自動在導航欄中對應添加或刪除對應導航項。
  4. 點擊保存寶貝后,會進行信息校驗;如果有錯誤,會在側邊導航中添加“出錯信息”大項,并展示錯誤項,同樣擁有定位功能。
  5. 點擊“頁面導航”或“出錯信息”兩個按鈕會展開/折疊對應的導航面板。

代碼實現

側邊導航欄的實現分為兩部分:模板(HTML)和控制邏輯(JS)。

一、模板

模板的簡化代碼如下,主要用各標簽的 class 和 id 表示不同的功能項。

頁面導航
<%* 有的商品沒有款式,所以做兼容 *%><

% var mods = {}; var property_name = (this.goods.style_param && this.goods.style_param.length) ? 'custom_classify' : 'goods_property'; var mods = [{key: 'goods-category', title: '在店鋪的分類', must:0}, {key: property_name, title:'款式/屬性', must:1}, {key: 'goods_title_nav', title: '標題/副標題/簡述', must:1}, {key: 'goods_tags', title:'標簽', must:0}, {key: 'mod_mlzz', title:'貨號/美麗制造貨號', must:0}, {key: 'sku_set', title:'SKU設置', must:1}, {key: 'price', title: '價格/庫存', must:1}, {key: 'freight', title:'運費', must:1}, {key: 'mod-pics', title:'圖片信息', must:0}]; mods.forEach(function(item) {%>

<%=item.title%> <%if(item.must){%> * <%}%>

<

%})%>

封面圖 *

<

%this.goods && this.goods.goods_detail && this.goods.goods_detail.forEach(function(item,index){%>

<%=item.title%><%if(item.key=='detail_product_detail'||item.key=='detail_product_photos'){%> * <%}%>

<

%})%>

<divclass="floatBar">
  <divclass="floatBar-title">頁面導航<spanclass="iconfont icon-up floatBar-title-btn"></span></div>
  <divclass="floatBar-content" id="floatBar">
  <%* 有的商品沒有款式,所以做兼容 *%>
  <%  var mods = {};
      var property_name = (this.goods.style_param && this.goods.style_param.length) ? 'custom_classify' : 'goods_property';
      var mods = [{key: 'goods-category', title: '在店鋪的分類', must:0}, {key: property_name, title:'款式/屬性', must:1}, {key: 'goods_title_nav', title: '標題/副標題/簡述', must:1}, {key: 'goods_tags', title:'標簽', must:0}, {key: 'mod_mlzz', title:'貨號/美麗制造貨號', must:0}, {key: 'sku_set', title:'SKU設置', must:1}, {key: 'price', title: '價格/庫存', must:1}, {key: 'freight', title:'運費', must:1}, {key: 'mod-pics', title:'圖片信息', must:0}];
      mods.forEach(function(item) {%>
 
    <divclass="floatBar-content-item"><divclass="progressBar"></div><spanclass="icon iconfont icon-dot"></span><ahref="javascript:void(0);" class="<%=item.key%>" data-cat-id="<%=item.key%>"><spanclass="item-title"><%=item.title%></span></a><%if(item.must){%><spanclass="must">*</span><%}%></div>
 
  <%})%>
 
    <divclass="child-nav-container" id="child-nav-container">
    <divclass="floatBar-content-item floatBar-content-detail-item first"><ahref="javascript:void(0);" data-cat-id="mod_gallery" class="mod_gallery">封面圖</a><spanclass="must">*</span></div>
 
    <%this.goods && this.goods.goods_detail && this.goods.goods_detail.forEach(function(item,index){%>
    <divclass="floatBar-content-item floatBar-content-detail-item"><ahref="javascript:void(0);" data-cat-id="<%=item.key%>" class="<%=item.key%>" title="<%=item.title%>"><%=item.title%></a><%if(item.key=='detail_product_detail'||item.key=='detail_product_photos'){%><spanclass="must">*</span><%}%></div>
    <%})%>
 
    </div>
 
    <divclass="floatBar-content-item last"><divclass="progressBar"></div><spanclass="icon iconfont icon-dot"></span><ahref="javascript:void(0);" class="goods_others" data-cat-id="goods_others"><spanclass="item-title">其他信息</span></a></div>
 
    <divclass="clear_f"></div>
  </div>
  <divclass="floatBar-title" style="display: none;">出錯信息<spanclass="iconfont icon-down floatBar-title-btn"></span></div>
  <divclass="floatBar-content" id="floatBarErr" style="display: none;">
  </div>
</div>

不同 class 表示的不同類型節點:

  1. floatBar :整個側邊導航模塊。
  2. floatBar-title :側邊導航不同模塊的 title,比如本例就包括“頁面導航”和“出錯信息”。
  3. floatBar-content :側邊導航一個模塊中的多個導航項容器。
  4. floatBar-content-item :導航項。
  5. child-nav-container :針對“圖片信息”模塊包含的子模塊創建的容器。
  6. floatBar-content-detail-item :“圖片信息”模塊下子模塊導航項。

其中每個導航項都有 data-cat-id 屬性,值同該項的 class 值。不同 id 表示的不同節點:

  1. floatBar :“頁面導航”導航項容器。
  2. floatBarErr :“出錯信息”導航項容器。
  3. child-nav-container :“圖片信息”下導航項容器。

二、控制邏輯

代碼模塊組織采用類 AMD 規范,不詳細介紹,本模塊名為sideNav.js。模塊內的一些全局變量如下:

// 當前導航所處的選項編號,用于僅當選項發生變化時再觸發_changeActive()
var curNavNo = -1;

// 導航菜單容器
var floatBar = $('#floatBar'),
    floatBarErr = $('#floatBarErr');

// 導航部分可見高度范圍
var viewMin = $('#floatBar').find('.floatBar-content-item:first-child').position().top,
    viewMax = $('#floatBar').outerHeight(true)-2;

// 判斷滾動到底部時需要減去的head目錄高度
var headerHeight = $('.body div.head').height();

// 當前已展開導航項(導航/錯誤提示)索引,0為導航,1為測試
var curNavSpread = 0;

var navsHeight = [floatBar.height(), '100%'];
// 當前導航所處的選項編號,用于僅當選項發生變化時再觸發_changeActive()
var curNavNo = -1;
 
// 導航菜單容器
var floatBar = $('#floatBar'),
    floatBarErr = $('#floatBarErr');
 
// 導航部分可見高度范圍
var viewMin = $('#floatBar').find('.floatBar-content-item:first-child').position().top,
    viewMax = $('#floatBar').outerHeight(true)-2;
 
// 判斷滾動到底部時需要減去的head目錄高度
var headerHeight = $('.body div.head').height();
 
// 當前已展開導航項(導航/錯誤提示)索引,0為導航,1為測試
var curNavSpread = 0;
 
var navsHeight = [floatBar.height(), '100%'];

現在針對 頁面效果 部分提出的幾個功能點,分別展示代碼。

1. 導航定位

/* * 點擊右側導航按鈕后改變樣式 * @param {obj} target 已點擊按鈕 * @return {none} / var _changeActive = function(target) { var eleName = '.' + target; floatBar.find('a.color-pink').removeClass('color-pink').prev().removeClass('icon-circle color-pink').addClass('icon-dot'); floatBar.find(eleName).addClass('color-pink').prev().removeClass('icon-dot').addClass('icon-circle color-pink'); };

/* * 綁定錯誤導航項點擊事件 / var _bindErrorNavClick = function () { $('#floatBarErr').delegate('.floatBar-content-item', 'click', function (item) { $('#content').animate({scrollTop: $('#'+$(this).data('cat-id')).position().top}, 150); }) }

/* * 綁定錯誤面板點擊事件 / var _bindErrorPanelClick = function () { $('#errPanel').delegate('.err-panel-item', 'click', function (item) { $('#content').animate({scrollTop: $(this).data('top')}, 150); $('#errPanel').hide(); }); };

/* * 綁定右側導航定位事件 / var bindNavClick = function() { floatBar.delegate('.floatBar-content-item a', 'click', function(e) { var eleId = $(this).data('cat-id'); $('#content').animate({scrollTop: $('#'+eleId).position().top}, 150); setTimeout(function(){ changeActive(eleId);}, 200); // IE9 hack 防止anchor的click觸發beforeunload事件 return false; }); bindErrorNavClick(); _bindErrorPanelClick(); };

/**
* 點擊右側導航按鈕后改變樣式
* @param  {obj} target 已點擊按鈕
* @return {none}
*/
var _changeActive = function(target) {
    var eleName = '.' + target;
    floatBar.find('a.color-pink').removeClass('color-pink').prev().removeClass('icon-circle color-pink').addClass('icon-dot');
    floatBar.find(eleName).addClass('color-pink').prev().removeClass('icon-dot').addClass('icon-circle color-pink');
};
 
 
/**
* 綁定錯誤導航項點擊事件
*/
var _bindErrorNavClick = function () {
    $('#floatBarErr').delegate('.floatBar-content-item', 'click', function (item) {
        $('#content').animate({scrollTop: $('#'+$(this).data('cat-id')).position().top}, 150);
    })
}
 
 
/**
* 綁定錯誤面板點擊事件
*/
var _bindErrorPanelClick = function () {
    $('#errPanel').delegate('.err-panel-item', 'click', function (item) {
        $('#content').animate({scrollTop: $(this).data('top')}, 150);
        $('#errPanel').hide();
    });
};
 
 
/**
* 綁定右側導航定位事件
*/
var bindNavClick = function() {
    floatBar.delegate('.floatBar-content-item a', 'click', function(e) {
        var eleId = $(this).data('cat-id');
        $('#content').animate({scrollTop: $('#'+eleId).position().top}, 150);
        setTimeout(function(){_changeActive(eleId);}, 200);
        // IE9 hack 防止anchor的click觸發beforeunload事件
        return false;
    });
    _bindErrorNavClick();
    _bindErrorPanelClick();
};

主要就是實現滾動效果,以及 _changeActive 用來改變導航項的選中樣式。

2. 滾動切換

該功能實現如下:

/* * 判斷到達頁面最底部改變導航顏色狀態 / var arriveBottom = function (mods) { // 暫時去掉逐級判斷 // for (var i = curNavNo + 1; i < mods.length; ++i) { // changeStateAndMove(i, mods); // }

_changeStateAndMove(mods.length-1, mods);

};

/* * 改變導航項狀態,并且視情況滾動右側導航條使選中項移動至視野內 / var changeStateAndMove = function (index, mods) { changeActive(mods[index]); curNavNo = index;

var navItemTop = floatBar.find('.'+mods[index]).parent().position().top;
// 40和478是導航部分可見高度范圍
if (navItemTop < viewMin || navItemTop > viewMax)
    floatBar.animate({scrollTop: navItemTop-35}, 200);

}

/* * 綁定窗口滾動事件 / var bindWindowScroll = function() { var mods = []; $.each(floatBar.find('.floatBar-content-item a'), function(index, item){ mods.push($(item).data('cat-id')); }); // 添加或刪除模塊時會重新綁定滾動事件,所以需要先取消之前綁定的事件 $('#content').unbind('scroll'); $('#content').scroll(function(e){ // 當前滾動位置 var curHeight = $(this).scrollTop(); // 容器總高度 var totalHeight = $('#content').find('div.add_goods').height();

if (curHeight + $(window).height() - headerHeight == totalHeight) {
        _arriveBottom(mods);
        return;
    }

    for (var i = 1; i < mods.length; ++i) {
        if (curHeight < $('#'+mods[i]).position().top-5) {
            if (curNavNo != i-1) {// 僅當區域發生切換時才改變導航項狀態
                _changeStateAndMove(i-1, mods);
            }
            break;
        }
    }
    if (i == mods.length && curNavNo != mods.length-1) {
        _changeStateAndMove(mods.length-1, mods);
    }
});

};

/**
* 判斷到達頁面最底部改變導航顏色狀態
*/
var _arriveBottom = function (mods) {
    // 暫時去掉逐級判斷
    // for (var i = curNavNo + 1; i < mods.length; ++i) {
    //     _changeStateAndMove(i, mods);
    // }
 
    _changeStateAndMove(mods.length-1, mods);
};
 
 
/**
* 改變導航項狀態,并且視情況滾動右側導航條使選中項移動至視野內
*/
var _changeStateAndMove = function (index, mods) {
    _changeActive(mods[index]);
    curNavNo = index;
 
    var navItemTop = floatBar.find('.'+mods[index]).parent().position().top;
    // 40和478是導航部分可見高度范圍
    if (navItemTop < viewMin || navItemTop > viewMax)
        floatBar.animate({scrollTop: navItemTop-35}, 200);
}
 
 
/**
* 綁定窗口滾動事件
*/
var bindWindowScroll = function() {
    var mods = [];
    $.each(floatBar.find('.floatBar-content-item a'), function(index, item){
        mods.push($(item).data('cat-id'));
    });
    // 添加或刪除模塊時會重新綁定滾動事件,所以需要先取消之前綁定的事件
    $('#content').unbind('scroll');
    $('#content').scroll(function(e){
        // 當前滾動位置
        var curHeight = $(this).scrollTop();
        // 容器總高度
        var totalHeight = $('#content').find('div.add_goods').height();
 
        if (curHeight + $(window).height() - headerHeight == totalHeight) {
            _arriveBottom(mods);
            return;
        }
 
        for (var i = 1; i < mods.length; ++i) {
            if (curHeight < $('#'+mods[i]).position().top-5) {
                if (curNavNo != i-1) {// 僅當區域發生切換時才改變導航項狀態
                    _changeStateAndMove(i-1, mods);
                }
                break;
            }
        }
        if (i == mods.length && curNavNo != mods.length-1) {
            _changeStateAndMove(mods.length-1, mods);
        }
    });
};
3. 添加/刪除導航項

這部分功能在自定義模塊中實現,在本模塊中不需要對自定義模塊進行特殊處理,他們對應的導航項都是普通的導航項,只要添加在 child-nav-container 這個容器中即可。

4. 錯誤信息面板

剛進到頁面時,“出錯信心”這個導航模塊是不展示的,只有當提交商品信息并出現校驗錯誤,才會動態生成錯誤信息導航欄。

/* * 點擊提交后清除已有錯誤導航 / var _clearErr = function () { floatBarErr.children().remove(); floatBarErr.height(0); }

/* * 初始化生成錯誤導航 / var initError = function (errNav) { _clearErr();

var con = '#floatBarErr';
$.each($(errNav), function(index, item) {
    var el = $(shareTmp('add_err_nav', {
        option: {id: item.id, title: item.title}
    })).appendTo(con);
});

// 盡在第一次提交時綁定標題點擊事件
if (!floatBarErr.is(':visible')) {
    _bindTitleClick();
    $('.floatBar').find('.floatBar-title:eq(1)').show();
    floatBarErr.show();
}

// 保證展開錯誤導航頁
curNavSpread = 0;
_changeRollUp(true);

};

/**
* 點擊提交后清除已有錯誤導航
*/
var _clearErr = function () {
    floatBarErr.children().remove();
    floatBarErr.height(0);
}
 
 
/**
* 初始化生成錯誤導航
*/
var initError = function (errNav) {
    _clearErr();
 
    var con = '#floatBarErr';
    $.each($(errNav), function(index, item) {
        var el = $(shareTmp('add_err_nav', {
            option: {id: item.id, title: item.title}
        })).appendTo(con);
    });
 
    // 盡在第一次提交時綁定標題點擊事件
    if (!floatBarErr.is(':visible')) {
        _bindTitleClick();
        $('.floatBar').find('.floatBar-title:eq(1)').show();
        floatBarErr.show();
    }
 
    // 保證展開錯誤導航頁
    curNavSpread = 0;
    _changeRollUp(true);
};
5. 導航模塊切換

點擊“頁面導航”或“出錯信息”兩個導航模塊的標題,可以展開/折疊對應模塊,代碼如下:

/* * 切換當前展開的導航區域 * @param isInit 初始化錯誤導航時調用為true,點擊標題時為false,用于記錄floatBar的高度 / var _changeRollUp = function (isInit) { var navs = ['floatBar', 'floatBarErr']; var eleRollup = $('#'+navs[curNavSpread]), eleSpread = $('#'+navs[1-curNavSpread]);

// 判斷如果當前是導航區要折疊,要留白
var paddingVal = curNavSpread ? 0 : 2,
    paddingOri = curNavSpread ? 10 : 0;
navsHeight[0] = isInit || curNavSpread ? navsHeight[0] : floatBar.height();
eleRollup.animate({height:0, paddingTop:paddingVal, paddingBottom:paddingVal}, 300);
eleSpread.animate({height:navsHeight[1-curNavSpread], paddingTop:paddingOri, paddingBottom:paddingOri}, 300);
eleRollup.prev().find('.floatBar-title-btn').removeClass('icon-up-big').addClass('icon-down-big');
eleSpread.prev().find('.floatBar-title-btn').removeClass('icon-down-big').addClass('icon-up-big');
curNavSpread = 1 - curNavSpread;

};

/* * 點擊導航標題切換展示區域 / var bindTitleClick = function () { $('.floatBar').delegate('.floatBar-title', 'click', function(e) { changeRollUp(false); }) };

/**
* 切換當前展開的導航區域
* @param isInit 初始化錯誤導航時調用為true,點擊標題時為false,用于記錄floatBar的高度
*/
var _changeRollUp = function (isInit) {
    var navs = ['floatBar', 'floatBarErr'];
    var eleRollup = $('#'+navs[curNavSpread]),
        eleSpread = $('#'+navs[1-curNavSpread]);
 
    // 判斷如果當前是導航區要折疊,要留白
    var paddingVal = curNavSpread ? 0 : 2,
        paddingOri = curNavSpread ? 10 : 0;
    navsHeight[0] = isInit || curNavSpread ? navsHeight[0] : floatBar.height();
    eleRollup.animate({height:0, paddingTop:paddingVal, paddingBottom:paddingVal}, 300);
    eleSpread.animate({height:navsHeight[1-curNavSpread], paddingTop:paddingOri, paddingBottom:paddingOri}, 300);
    eleRollup.prev().find('.floatBar-title-btn').removeClass('icon-up-big').addClass('icon-down-big');
    eleSpread.prev().find('.floatBar-title-btn').removeClass('icon-down-big').addClass('icon-up-big');
    curNavSpread = 1 - curNavSpread;
};
 
 
/**
* 點擊導航標題切換展示區域
*/
var _bindTitleClick = function () {
    $('.floatBar').delegate('.floatBar-title', 'click', function(e) {
        _changeRollUp(false);
    })
};

代碼總結

以上就是主要的代碼實現,最后只要將 initError 、 bindNavClick 、 bindWindowScroll 這三個方法作為模塊接口暴露出去即可。代碼沒有充分考慮頁面性能等因素,后續找時間再優化下。

來自: http://nodefe.com/js-dynamic-sidenav/

 本文由用戶 MagicQ123 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!