2012-10-08 19 views
33

tengo una variable $ scope.first_unread_id alcance que se define en mi controlador. En mi plantilla, que tengo:AngularJS directiva para desplazarse a un elemento dado

<div id="items" > 
    <ul class="standard-list"> 
    <li ng-repeat="item in items" scroll-to-id="first_unread_id"> 
    <span class="content">{{ item.content }}</span> 
    </li> 
    </ul> 
</div> 

y mi directiva se parece a:

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); 
    } 
    } 

}); 

funciona, sin embargo, dos preguntas:

  1. ¿Hay una mejor manera de conseguir la "first_unread_id" fuera del alcance del controlador en el ámbito directo que interroga. $ parent? Esto parece un poco 'asqueroso'. Esperaba poder pasar eso a través de la vista al directo como un parámetro sin tener que repetir eso en cualquier elemento li.

  2. ¿Hay una mejor manera de evitar la necesidad de la llamada setTimeout()? Sin él, funciona veces - Imagino que debido a la diferencia en el tiempo de diseño. Entiendo que la sintaxis que he utilizado es definir una función de enlace, pero no me queda claro si se trata de un enlace previo o posterior por defecto, y si eso es importante para mi problema.

+0

https://docs.angularjs.org/api/ng/service/$anchorScroll – Blazemonger

+0

Ha comprobado fuera AnchorScroll de Angular? [Servicio AnchorScroll] (https://docs.angularjs.org/api/ng/service/$anchorScroll) –

Respuesta

39
  1. No debería ser necesario el alcance de los padres $ -. Puesto que heredará el valor del alcance de los padres, y cuando los cambios en el alcance de los padres va a ser transmitido.
  2. El valor predeterminado es una función de enlace posterior. ¿Tiene algunas imágenes o algo de carga que haría que el diseño de la página cambie poco después de la carga inicial? ¿Has probado un setTimeout sin tiempo, por ejemplo, setTimeout (function() {})? Esto aseguraría que esto iría 'uno después' de que se haga todo lo demás.
  3. También cambiaría un poco la lógica de su directiva para hacerla más general. Me gustaría desplazarme al elemento si una condición dada es verdadera.

Éstos son los 3 cambios:

html:

<div id="items" > 
    <ul class="standard-list"> 
    <li ng-repeat="item in items" scroll-if="item.id == first_unread_id"> 
     <span class="content">{{ item.content }}</span> 
    </li> 
    </ul> 
</div> 

JS:

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

¿Por qué no usar $ timeout? Además, ¿por qué $ eval en lugar de $ parse? ¿No sería $ parse más angular-y? – alalonde

+0

Estamos haciendo algo por única vez aquí, por lo que $ eval parece funcionar. $ parse generalmente se usa para analizar algo y luego guardarlo para usarlo más tarde. Y $ timeout generalmente se utiliza para llamar a un resumen cuando se activa, que en realidad no necesitamos aquí. –

+6

$ timeout (fn, delay, false) - ¡y sin comprobación sucia! –

11

Suponiendo que el elemento principal es en la que nos desplazamos, esto funciona para mí :

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); 
     } 
    }); 
    } 
}); 
+0

Gracias por '$ watch()', en mi caso la condición se configuró dinámicamente. –

6

Terminé con el siguiente código (que no depende de jQ) que también funciona si el elemento desplazable no es la ventana.

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.scrollTop = elemOffset - topMargin; 
       } else if (elemOffset + elemHeight + bottomMargin > sp.scrollTop + sp.clientHeight) { 
        sp.scrollTop = elemOffset + elemHeight + bottomMargin - sp.clientHeight; 
       } 
      } 
     }); 
    } 
}); 
1

En combinación con la interfaz de usuario del router $uiViewScroll que terminó con la siguiente directiva:

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

En combinación con @uri, esto funciona para el contenido dinámico con ui-router y en stateChangeSuccess.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); 

    }); 
3

Igual respuesta aceptada, pero utiliza el incorporado en javascript método "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"}); 
      } 
     }); 
    } 
}); 
0

Para una respuesta tomando lo mejor de las respuestas aquí, en ES6:

archivo: 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"}); 
      } 
     }); 
    } 
} 

archivo scroll.module.js

import ScrollDirective from './scroll.directive'; 

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

Después de importar en su proyecto, que se puede utilizar en el su html:

<div id="items" > 
    <ul class="standard-list"> 
    <li ng-repeat="item in items" ui-scroll="true"> 
    <span class="content">{{ item.content }}</span> 
    </li> 
    </ul> 
</div> 
Cuestiones relacionadas