Ответ 1
К сожалению, этот вопрос не получил такого большого внимания, как мне бы хотелось... И у меня нет времени, чтобы написать подробное объяснение с фоном, скриншотами профайлера и т.д. Но я нашел решение, и я надеюсь, что это поможет кому-то другому заниматься одной и той же проблемой.
Заключение
Цикл $digest в любом реалистичном среднесрочном приложении не сможет обрабатывать обновление 100 мс.
Учитывая требование setInterval
, я просто полностью обходил цикл, вместо этого предпочитая транслировать изменение состояния только при обнаружении различных измерений (используя собственные свойства offsetWidth/Height
).
Запуск с интервалом 100 мс с единственным манифестом области $root
дает наилучшие результаты всего, что я тестировал, с ~ 10,2% активной вкладкой CPU на моем 2.4Ghz i7 2013 MBP - приемлемым по сравнению с ~ 84% при $interval
.
Любые комментарии и критические замечания приветствуются, в противном случае - это автономная директива! Очевидно, что вы можете получить креатив с областью действия и/или атрибутами для настройки наблюдателей, но в интересах оставаться на теме я попытался опустить лишний код.
Он будет контролировать и связывать изменения размера элемента в свойстве области по вашему выбору, для N элементов с линейной сложностью. Я не могу гарантировать, что это самый быстрый/лучший способ сделать это, но это самая быстрая реализация, которую я мог бы быстро разработать, отслеживая изменения измерения уровня DOM на уровне состояния:
app.directive('ngSize', ['$rootScope', function($root) {
return {
scope: {
size: '=ngSize'
},
link: function($scope, element, attrs) {
$root.ngSizeDimensions = (angular.isArray($root.ngSizeDimensions)) ? $root.ngSizeDimensions : [];
$root.ngSizeWatch = (angular.isArray($root.ngSizeWatch)) ? $root.ngSizeWatch : [];
var handler = function() {
angular.forEach($root.ngSizeWatch, function(el, i) {
// Dimensions Not Equal?
if ($root.ngSizeDimensions[i][0] != el.offsetWidth || $root.ngSizeDimensions[i][1] != el.offsetHeight) {
// Update Them
$root.ngSizeDimensions[i] = [el.offsetWidth, el.offsetHeight];
// Update Scope?
$root.$broadcast('size::changed', i);
}
});
};
// Add Element to Chain?
var exists = false;
angular.forEach($root.ngSizeWatch, function(el, i) { if (el === element[0]) exists = i });
// Ok.
if (exists === false) {
$root.ngSizeWatch.push(element[0]);
$root.ngSizeDimensions.push([element[0].offsetWidth, element[0].offsetHeight]);
exists = $root.ngSizeWatch.length-1;
}
// Update Scope?
$scope.$on('size::changed', function(event, i) {
// Relevant to the element attached to *this* directive
if (i === exists) {
$scope.size = {
width: $root.ngSizeDimensions[i][0],
height: $root.ngSizeDimensions[i][1]
};
}
});
// Refresh: 100ms
if (!window.ngSizeHandler) window.ngSizeHandler = setInterval(handler, 100);
// Window Resize?
// angular.element(window).on('resize', handler);
}
};
}]);