實現效果:
1.點擊按鈕展開下拉列表
2.點擊下拉列表中的選項進行選擇,隨後收起下拉列表
3.點擊除下拉列表外的位置(包括按鈕),收起下拉列表
效果圖如下所示:
完整代碼請查看:
https://github.com/wolf-wolf/pullDownList.git
實現環境
1.angular
2.sublime
#基本結構:
組件的基本HTML結構如下所示:
<!-- 外部包裹層 -->
<div class="main" ng-click="toggleList($event)">
<!-- 按鈕展示 -->
<h4 class="open-btn">選擇列表3</h4>
<!-- 下拉列表面板 -->
<div class="list-wrapper" ng-show="flag.showList">
<ul class="list">
<!-- 選項 -->
<li class="item" ng-repeat="item in list" ng-click="selectItem($event,item)">
<span class="item-name" ng-bind="item.name"></span>
</li>
</ul>
</div>
</div>
實現
方法一:
基本思路
在document上監聽click事件,進而關閉打開的下拉列表
主要代碼
JavaScript代碼
//列表是否打開的判斷標誌
$scope.flag = {
showList: false
};
//在document上監聽click函數
$document.bind('click', function(event) {
_closeList();//關閉下拉列表
$scope.$apply();//爲了保證angular的數據同步
});
/**
* 關閉列表函數
*/
function _closeList() {
$scope.flag.showList = false;
}
總結
上述方法實現簡單,理解容易,但存在問題,如果在同一頁面中存在多個組件可供點擊,並且存在部分組件針對click事件使用event.stopPropagation()
或event.stopImmediatePropagation()
方法阻止事件冒泡,則點擊事件無法到達document,進而無法達到自動關閉下拉列表的效果。
方法二
###基本思路
在列表打開時,在body
上添加遮罩層,並將其z-index
屬性設置爲最高,進而遮擋所有該頁面的其他組件,同時監聽發生在遮罩層上的點擊事件,來達到自動關閉下拉列表的效果。
主要代碼
####JavaScript
/**
* 切換列表關閉和打開狀態
* @param {Object} event 事件參數,用於阻止事件冒泡
*/
$scope.toggleList = function(event) {
if ($scope.flag.showList) { //如果列表打開則關閉
_closeList();
} else { //如果列表是關閉狀態則,創建遮罩層,並打開列表
var _mask = document.createElement('div');
_mask.className = 'drop-down-mask-' + _uniquePrefix;
_mask.style.position = 'absolute';//使用絕對性爲
_mask.style.top = '0';
_mask.style.left = '0';
_mask.style.width = '100%';
_mask.style.height = '100%';
_mask.style.zIndex = '99998';
_mask.style.background = 'rgba(255, 255, 255, 0)';//設置遮罩層背景顏色爲透明
//爲遮罩層添加click方法
_mask.addEventListener('click', function(event) {
_closeList();
$scope.$apply();
});
$document[0].body.append(_mask);
$scope.flag.showList = true;
}
};
/**
* 關閉列表函數,將列表是否打開的判斷標誌設置爲false,並且清除遮罩層
*/
function _closeList() {
$scope.flag.showList = false;
//獲取遮罩層元素
var _mask = angular.element(document.getElementsByClassName('drop-down-mask-' + _uniquePrefix));
if (_mask) {
_mask.remove();//移除遮罩層
}
}
HTML
<div class="main">
<button class="open-btn" ng-click="toggleList($event)">選擇列表1</button>
<div class="list-wrapper" ng-show="flag.showList">
<ul class="list">
<li class="item" ng-repeat="item in list" ng-click="selectItem($event,item)">
<span class="item-name" ng-bind="item.name"></span>
</li>
</ul>
</div>
</div>
總結
- 下拉列表打開後添加遮罩層
- 遮罩層插入到
body
下,body
的position
屬性爲relative
,遮罩層的position
屬性爲absolute
,以確保遮蓋整個頁面 - 在創建遮罩層時,需要判斷是否已經存在遮罩,在這裏,因爲打開下拉列表表示遮罩層已經創建,所以判斷下拉列表是否打開和判斷遮罩層是否已經存在具有等同效果。如果未做判斷,則會出現多次點擊生成多個遮罩層的效果,從而在點擊遮罩層時,需要多次點擊才能保證其他組件的可用性
- 在關閉下拉列表的同時,一定要移除遮罩層的存在
缺陷:因爲有遮罩層的存在所以在點擊非下拉列表的位置時,點擊事件發生在遮罩層上,如果想要在下拉列表打開的情況下,將焦點定位到其他組件,則需要二次點擊。
TIP:
此方法是select2組件所採用的方式,可通過鏈接查看官網。
方法三
基本思路
爲div
添加tabindex
屬性,使其可以獲得和失去焦點。監聽blur
事件做到自動關閉下拉列表的效果
主要代碼
JavaScript
/**
* 關閉列表
* @param {Object} event 事件參數,用於阻止事件冒泡
*/
$scope.closeList = function(event) {
event.stopPropagation();
_closeList();
}
/**
* 切換列表關閉和打開狀態
* @param {Object} event 事件參數,用於阻止事件冒泡
*/
$scope.toggleList = function(event) {
event.stopImmediatePropagation();
if ($scope.flag.showList) {
_closeList();
} else {
$scope.flag.showList = true;
}
};
/**
* 關閉列表函數
*/
function _closeList() {
$scope.flag.showList = false;
}
HTML
<div class="main" tabindex="-1" ng-blur="closeList($event)" ng-click="toggleList($event)">
<h4 class="open-btn">選擇列表2</h4>
<div class="list-wrapper" ng-show="flag.showList">
<ul class="list">
<li class="item" ng-repeat="item in list" ng-click="selectItem($event,item)">
<span class="item-name" ng-bind="item.name"></span>
</li>
</ul>
</div>
</div>
總結
- 在最外層包裹層,添加
tabindex
屬性,這裏將其值置爲-1
,目的是保證在使用tab
鍵進行組件焦點切換時不會切換到本組件 - 監聽
blur
事件而非click
事件 - 對按鈕部分監聽
click
事件,以實現點擊按鈕切換下拉列表打開狀態的效果
TIP:
爲什麼不監聽focus
事件:因爲在獲取焦點後,focus
事件步不再會被觸發,則無法通過點擊按鈕來關閉下拉列表。同時focus
事件發生在click
事件之前,如果兩個事件同時綁定打開下拉列表的處理函數,則會產生閃爍效果,具體情況,看官可自行嘗試~~
tabindex
屬性:請查看W3CSchool
如有錯誤請多多包涵,敬請反饋,期待一起進步~~~
再見,祝好~~!