AngularJS directive to scroll to a given item

匿名 (未验证) 提交于 2019-12-03 00:59:01

问题:

I have a scope variable $scope.first_unread_id which is defined in my controller. In my template, I have:

  • {{ item.content }}

and my directive looks like:

angular.module('ScrollToId', []). directive('scrollToId', function () {   return function (scope, element, attributes) {     var id = scope.$parent[attributes["scrollToId"]];     if (id === scope.item.id) {       setTimeout(function () {         window.scrollTo(0, element[0].offsetTop - 100)       }, 20);     }   }  }); 

it works, however, two questions:

  1. Is there a better way of getting the "first_unread_id" off the controller scope into the direct than interrogating scope.$parent? This seems a bit 'icky'. I was hoping I could pass that through the view to the direct as a parameter w/o having to repeat that on ever li element.

  2. Is there a better way to avoid the need of the setTimeout() call? Without it, it works sometimes - I imagine due to difference in timing of layout. I understand the syntax I have used is defining a link function - but it isn't clear to me if that is a pre or post-link by default - and if that even matters for my issue.

回答1:

  1. You shouldn't need the scope.$parent - since it will inherit the value from the parent scope, and when it changes in the parent scope it will be passed down.
  2. The default is a post-link function. Do you have some images or something loading that would make the page layout change shortly after initial load? Have you tried a setTimeout with no time on it, eg setTimeout(function(){})? This would make sure this would go 'one after' everything else is done.
  3. I would also change the logic of your directive a bit to make it more general. I would make it scroll to the element if a given condition is true.

Here are those 3 changes:

html:

  • {{ item.content }}

JS:

app.directive('scrollIf', function () {   return function (scope, element, attributes) {     setTimeout(function () {       if (scope.$eval(attributes.scrollIf)) {         window.scrollTo(0, element[0].offsetTop - 100)       }     });   } }); 


回答2:

Assuming that the parent element is the one where we scroll, this works for me:

app.directive('scrollIf', function () {   return function(scope, element, attrs) {     scope.$watch(attrs.scrollIf, function(value) {       if (value) {         // Scroll to ad.         var pos = $(element).position().top + $(element).parent().scrollTop();         $(element).parent().animate({             scrollTop : pos         }, 1000);       }     });   } }); 


回答3:

I ended up with the following code (which does not depend on jQ) which also works if the scrolling element is not the window.

app.directive('scrollIf', function () {     var getScrollingParent = function(element) {         element = element.parentElement;         while (element) {             if (element.scrollHeight !== element.clientHeight) {                 return element;             }             element = element.parentElement;         }         return null;     };     return function (scope, element, attrs) {         scope.$watch(attrs.scrollIf, function(value) {             if (value) {                 var sp = getScrollingParent(element[0]);                 var topMargin = parseInt(attrs.scrollMarginTop) || 0;                 var bottomMargin = parseInt(attrs.scrollMarginBottom) || 0;                 var elemOffset = element[0].offsetTop;                 var elemHeight = element[0].clientHeight;                  if (elemOffset - topMargin  sp.scrollTop + sp.clientHeight) {                     sp.scrollTop = elemOffset + elemHeight + bottomMargin - sp.clientHeight;                 }             }         });     } }); 


回答4:

Same as accepted answer, but uses the javascript built-in method "scrollIntoView":

angular.module('main').directive('scrollIf', function() {     return function(scope, element, attrs) {         scope.$watch(attrs.scrollIf, function(value) {             if (value) {                 element[0].scrollIntoView({block: "end", behavior: "smooth"});             }         });     } }); 


回答5:

In combination with UI Router's $uiViewScroll I ended up with the following directive:

app.directive('scrollIf', function ($uiViewScroll) {     return function (scope, element, attrs) {         scope.$watch(attrs.scrollIf, function(value) {             if (value) {                 $uiViewScroll(element);             }         });     } }); 


回答6:

In combo with @uri, this works for my dynamic content with ui-router and stateChangeSuccess in .run:

$rootScope.$on('$stateChangeSuccess',function(newRoute, oldRoute){          setTimeout(function () {             var postScroll = $state.params.postTitle;             var element = $('#'+postScroll);             var pos = $(element).position().top - 100 + $(element).parent().scrollTop();             $('body').animate({                 scrollTop : pos             }, 1000);         }, 1000);      }); 


回答7:

For an answer taking the best of the answers here, in ES6:

File: scroll.directive.js

export default function ScrollDirective() {     return {         restrict: 'A',         scope: {             uiScroll: '='         },         link: link     };      function link($scope, $element) {         setTimeout(() => {             if ($scope.uiScroll) {                 $element[0].scrollIntoView({block: "end", behavior: "smooth"});             }         });     } } 

File scroll.module.js

import ScrollDirective from './scroll.directive';  export default angular.module('app.components.scroll', [])     .directive('uiScroll', ScrollDirective); 

After importing it in your project, you can use it in the your html:

  • {{ item.content }}


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!