Вот скрипка моего решения, которое:
- Доступен (вы можете использовать клавиши табуляции для активации/деактивации).
- Позволяет пользователю наводить курсор на всплывающее окно, при этом всплывающее окно остается открытым.
- Разрешает несколько всплывающих окон на странице, но только одно всплывающее окно может быть активировано в любой момент времени.
- Не зависит от какой-либо третьей стороны, хотя стили всплывающих окон начальной загрузки были заимствованы.
Это работает так: мы создаем сколько угодно всплывающих окон, которые у нас будут на странице, в массиве popover
(см. TODO в комментариях о том, как это подключить).
Затем каждый раз, когда пользователь наводит курсор мыши на элемент, который должен вызвать всплывающее окно, мы активируем это конкретное всплывающее окно в массиве popover
. Когда пользователь больше не наводит курсор на элемент, мы устанавливаем тайм-аут для этого конкретного popover
в массиве. Если этот тайм-аут истек, он выполняет быструю проверку, чтобы увидеть, перенавел ли пользователь курсор или перефокусировал (с помощью табуляции) элемент. Если да, то мы сохраняем всплывающее окно живым. Если нет, мы скрываем всплывающее окно.
Для CSS я не хотел полагаться на использование начальной загрузки, поэтому я позаимствовал стили непосредственно из начальной загрузки. Если вы попытаетесь использовать стили всплывающих окон бутстрапа, вы можете столкнуться с каким-то странным поведением, когда загрузчик запускает свои собственные скрипты в нашем пользовательском всплывающем окне, что нам не нужно.
HTML:
<section>
<a href="#"
ng-mouseover="showPopover(i)"
ng-mouseleave="hidePopover(i)"
ng-focus="showPopover(i)"
ng-blur="hidePopover(i)">
I trigger a popover - {{i}}
</a>
<popover popover-show="popover[i].popoverTracker">
<div class="arrow"></div>
<div class="custom-popover-content"
ng-mouseover="showPopover(i)"
ng-mouseleave="hidePopover(i)"
ng-focus="showPopover(i)"
ng-blur="hidePopover(i)">
<a href="#"
ng-focus="showPopover(i)"
ng-blur="hidePopover(i)">You can tab into me, I'm accessible!</a>
<br/>
<a href="#"
ng-focus="showPopover(i)"
ng-blur="hidePopover(i)">You can tab into me, I'm accessible!</a>
</div>
</popover>
</section>
Угловой контроллер и директива:
angular.module('controllers', []);
angular.module('directives', []);
angular.module('myApp', ['ngAnimate', 'controllers', 'directives']);
angular.module('controllers')
.controller('MainCtrl', function ($scope, $timeout) {
$scope.popover = [];
(function init() {
// TODO: Make this dynamic so that we can pass it a value and it will generate the right amount
// Initializing the array of popovers on startup
createPopoverTrackers(20);
})();
// Creating an array of popovers equal to the number of popovers on the page
function createPopoverTrackers(count) {
for(var i = 0; i < count; i++) {
$scope.popover.push({
popoverTracker: false,
popoverKeepAlive: false,
timer: null
})
}
}
// A user has focused on an element that has an associated popover
$scope.queueOpenPopover = function(index) {
// Show our specified tracker
$scope.popover[index].popoverTracker = true;
// Hide the rest
Object.keys($scope.popover)
.filter(function(trackerIndex) {
return trackerIndex != index
})
.forEach(function(trackerIndex) {
$scope.popover[trackerIndex].popoverTracker = false;
$scope.popover[trackerIndex].popoverKeepAlive = false;
const timer = $scope.popover[trackerIndex].timer;
if(timer) {
$timeout.cancel(timer);
$scope.popover[trackerIndex].timer = null;
}
})
};
// Queuing up the demise of the popover
$scope.queueKillPopover = function(index) {
$scope.popover[index].timer = $timeout(function() {
if (!$scope.popover[index].popoverKeepAlive) {
// Popover or the popover trigger were not hovered within the time limit, kill it!
$scope.popover[index].popoverTracker = false;
}
}, 700);
};
// When a user focuses into the actual popover itself or it's trigger, we need to keep it alive
$scope.showPopover = function(index) {
$scope.popover[index].popoverKeepAlive = true;
$scope.queueOpenPopover(index);
};
// The user has removed focus from the popover or it's trigger, set this to false so the timer knows to kill it
$scope.hidePopover = function(index) {
$scope.popover[index].popoverKeepAlive = false;
$scope.queueKillPopover(index);
};
});
angular.module('directives')
.directive('popover', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
'popoverShow': '='
},
template: '<div class="custom-popover bottom" ng-show="popoverShow" ng-transclude></div>'
};
});
CSS позаимствован из бутстрапа:
.custom-popover {
position: absolute;
z-index: 1010;
max-width: 276px;
padding: 1px;
text-align: left;
white-space: normal;
background-color: #fff;
border: 1px solid rgba(0,0,0,0.2);
border-radius: 6px;
box-shadow: 0 5px 10px rgba(0,0,0,0.2);
background-clip: padding-box;
}
.custom-popover .arrow,
.custom-popover .arrow:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.custom-popover .arrow {
border-width: 11px;
}
.custom-popover .arrow:after {
border-width: 10px;
content: "";
}
.custom-popover.bottom {
margin-top: 10px;
}
.custom-popover.bottom .arrow {
top: -11px;
left: 50%;
margin-left: -11px;
border-bottom-color: rgba(0, 0, 0, 0.25);
border-top-width: 0;
}
.custom-popover.bottom .arrow:after {
top: 1px;
margin-left: -10px;
border-bottom-color: #ffffff;
border-top-width: 0;
content: " ";
}
.custom-popover-content {
padding: 9px 14px;
}
person
maxshuty
schedule
13.03.2020