Maison >interface Web >js tutoriel >Une brève discussion sur la mise en œuvre améliorée des compétences Sticky composants_javascript
Dans l'article précédentUtilisation de la méthode getBoundingClientRect pour implémenter un composant collant simple a présenté une implémentation simple du composant collant Après y avoir réfléchi au cours des deux derniers jours, j'ai découvert qu'il y avait plus d'implémentations fournies. La dernière fois, les inconvénients sont que les effets obtenus sur d'autres sites Web sont également quelque peu différents lors de la suppression. La méthode de suppression fournie la dernière fois n'était pas bonne. Sur la base de ce qui précède, cet article propose une version améliorée du composant collant. complet, j'espère que vous êtes intéressé à le lire.
1. Problèmes avec les anciennes versions
Il y a plusieurs problèmes dans la mise en œuvre du composant collant précédent :
Premièrement, en termes d'effet de sticky, avant et après la fixation de l'élément sticky, ce qui ne changera pas, c'est la position par rapport au côté gauche du navigateur et la largeur totale de l'élément sticky. Ce qui peut changer, c'est. la position par rapport au haut ou au bas du navigateur et la hauteur de l'élément collant. Dans l'implémentation fournie ci-dessus, ces deux dernières valeurs changeantes sont considérées comme des valeurs constantes. Pourquoi la valeur supérieure ou la valeur inférieure est-elle toujours 0 lorsqu'elle est fixe ? Bien sûr, il peut être différent de 0, comme top : 20px, bottom : 15px. Dans certaines scènes, l'ajout de tels décalages rendra l'effet collant meilleur, comme l'exemple de composant affixe utilisé dans la documentation officielle de bootstrap (ce composant La fonction est similaire au composant collant implémenté dans cet article) :
Il définit la position par rapport au haut du navigateur en haut : 20 px lorsqu'il est corrigé. Il en va de même pour la hauteur des éléments collants Afin d'afficher un meilleur effet une fois fixés, il est également très courant d'ajuster la hauteur de ligne d'origine ou le rembourrage supérieur et d'autres attributs liés à la hauteur. cette page de Tmall Huabei, ce contenu de bloc utilise le composant collant :
Avant fixe, la hauteur de l'élément collant est :
Une fois fixée, la hauteur de l'élément collant est :
Deuxièmement, lors de l'annulation de la fixation, en prenant comme exemple l'élément collant fixé en haut, la mise en œuvre proposée ci-dessus consiste à annuler directement la position de l'élément collant lorsque la distance entre l'élément cible et le haut du navigateur est less than stickyHeight : attribut fixe, l'élément collant est immédiatement restauré au flux de document normal et l'effet est :
Il disparaît immédiatement lorsqu'il atteint le point critique, mais l'effet de Tmall Huabei n'est pas comme ça :
Il ne disparaît pas immédiatement lorsqu'il atteint le point critique, mais réajuste la valeur supérieure de l'élément collant afin qu'il puisse défiler vers le haut avec le contenu principal de la page Web en conjonction avec la barre de défilement :
D'un point de vue expérience, il est évident que l'effet de Tmall Huabei est meilleur. D'un point de vue fonctionnel, l'implémentation proposée ci-dessus présente un défaut fatal : lorsque la hauteur de l'élément collant est très grande, il dépasse les capacités du navigateur. Lors de l'affichage de la hauteur de la zone, il y aura un bug qui, peu importe la façon dont vous faites défiler, vous ne pouvez pas parcourir tout le contenu des éléments collants. Si vous êtes intéressé, vous pouvez essayer le code implémenté la dernière fois sur. la barre latérale de votre blog. J'ai essayé et trouvé ce problème, j'ai donc voulu améliorer le composant collant : (
Troisièmement, la dernière implémentation présente encore plusieurs lacunes :
1) documentElement.clientHeight n'est pas mis en cache, ce qui entraîne sa réacquisition à chaque fois que le point critique est jugé :
2) La valeur par défaut de l'intervalle de rappel de défilement est trop grande et doit être définie plus petite cette fois, elle est de 5 et le bootstrap utilise 1. Ce n'est qu'ainsi que l'effet peut être garanti
;
3) Dans certains scénarios, la largeur de l'élément collant peut ne pas avoir besoin d'être réinitialisée lorsqu'un redimensionnement est requis. Une option doit être ajoutée pour le contrôler
.
4) Lorsque l'élément collant est fixe et non fixé, une fonction de rappel doit être fournie afin que d'autres composants puissent faire des choses à des points clés lorsqu'ils dépendent de ce composant.
2. Comment s'améliorer
Les options des composants ont été redéfinies :
var DEFAULTS = { target: '', //target元素的jq选择器 type: 'top', //固定的位置,top | bottom,默认为top,表示固定在顶部 wait: 5, //scroll事件回调的间隔 stickyOffset: 0, //固定时距离浏览器可视区顶部或底部的偏移,用来设置top跟bottom属性的值,默认为0 isFixedWidth: true, //sticky元素宽度是否固定,默认为true,如果是自适应的宽度,需设置为false getStickyWidth: undefined, //用来获取sticky元素宽度的回调,在不传该参数的情况下,stickyWidth将设置为sticky元素的offsetWidth unStickyDistance: undefined, //该参数决定sticky元素何时进入dynamicSticky状态 onSticky: undefined, ///sticky元素固定时的回调 onUnSticky: undefined ///sticky元素取消固定时的回调 };
Ceux en gras sont nouveaux ou modifiés. La hauteur d'origine a été supprimée et remplacée par unStickyDistance. Lors de la correction, la position par rapport au haut ou au bas du navigateur est spécifiée avec stickyOffset, de sorte qu'il n'est pas nécessaire d'écrire la valeur de l'attribut haut ou bas dans le CSS de .sticky--in-top ou .sticky--in -bas. Si isFixedWidth est faux, un rappel pour actualiser la largeur de l'élément collant lors du redimensionnement sera ajouté :
!opts.isFixedWidth && $win.resize(throttle(function () { setStickyWidth(); $elem.hasClass(className) && $elem.css('width', stickyWidth); sticky(); }, opts.wait));
Par rapport à la dernière fois, le problème dans cette implémentation est le traitement logique lors de l'annulation de la fixation. La dernière fois, l'élément collant n'avait que deux états, collant ou non collant. Cette fois, l'état collant est divisé en. staticSticky et DynamicSticky. Le premier représente l'état collant dans lequel la valeur supérieure ou inférieure reste inchangée. Le second représente l'état collant dans lequel la valeur supérieure ou inférieure change. à annuler. Afin de résoudre ce problème plus clairement, le jugement initial est Les points critiques et le code qui effectue différents traitements à différents points critiques sont restructurés comme suit :
setSticky = function () { !$elem.hasClass(className) && $elem.addClass(className).css('width', stickyWidth) && (typeof opts.onSticky == 'function' && opts.onSticky($elem, $target)); return true; }, states = { staticSticky: function () { setSticky() && $elem.css(opts.type, opts.stickyOffset); }, dynamicSticky: function (rect) { setSticky() && $elem.css(opts.type, rules[opts.type].getDynamicOffset(rect)); }, unSticky: function () { $elem.hasClass(className) && $elem.removeClass(className).css('width', '').css(opts.type, '') && (typeof opts.onUnSticky == 'function' && opts.onUnSticky($elem, $target)); } }, rules = { top: { getState: function (rect) { if (rect.top < 0 && (rect.bottom - unStickyDistance) > 0) return 'staticSticky'; else if ((rect.bottom - unStickyDistance) <= 0 && rect.bottom > 0) return 'dynamicSticky'; else return 'unSticky'; }, getDynamicOffset: function (rect) { return -(unStickyDistance - rect.bottom); } }, bottom: { getState: function (rect) { if (rect.bottom > docClientHeight && (rect.top + unStickyDistance) < docClientHeight) return 'staticSticky'; else if ((rect.top + unStickyDistance) >= docClientHeight && rect.top < docClientHeight) return 'dynamicSticky'; else return 'unSticky'; }, getDynamicOffset: function (rect) { return -(unStickyDistance + rect.top - docClientHeight); } } } $win.scroll(throttle(sticky, opts.wait)); function sticky() { var rect = $target[0].getBoundingClientRect(), curState = rules[opts.type].getState(rect); states[curState](rect); }
Il y a un peu l'idée du modèle étatique dedans, mais c'est plus concis. Quand j'ai écrit ce code, je voulais vraiment utiliser la machine à états que j'avais connue auparavant. Je pensais qu'il était tout à fait possible de l'écrire à l'aide d'une machine à états, mais je voulais juste éviter qu'une bibliothèque de classes ne soit référencée. attendez un jour. Réessayez lorsque vous souhaitez pratiquer les machines à états.
La mise en œuvre globale est la suivante :
var Sticky = (function ($) { function throttle(func, wait) { var timer = null; return function () { var self = this, args = arguments; if (timer) clearTimeout(timer); timer = setTimeout(function () { return typeof func === 'function' && func.apply(self, args); }, wait); } } var DEFAULTS = { target: '', //target元素的jq选择器 type: 'top', //固定的位置,top | bottom,默认为top,表示固定在顶部 wait: 5, //scroll事件回调的间隔 stickyOffset: 0, //固定时距离浏览器可视区顶部或底部的偏移,用来设置top跟bottom属性的值,默认为0 isFixedWidth: true, //sticky元素宽度是否固定,默认为true,如果是自适应的宽度,需设置为false getStickyWidth: undefined, //用来获取sticky元素宽度的回调,在不传该参数的情况下,stickyWidth将设置为sticky元素的offsetWidth unStickyDistance: undefined, //该参数决定sticky元素何时进入dynamicSticky状态 onSticky: undefined, ///sticky元素固定时的回调 onUnSticky: undefined ///sticky元素取消固定时的回调 }; return function (elem, opts) { var $elem = $(elem); opts = $.extend({}, DEFAULTS, opts || {}, $elem.data() || {}); var $target = $(opts.target); if (!$elem.length || !$target.length) return; var stickyWidth, setStickyWidth = function () { stickyWidth = typeof opts.getStickyWidth === 'function' && opts.getStickyWidth($elem) || $elem[0].offsetWidth; }, docClientHeight = document.documentElement.clientHeight, unStickyDistance = opts.unStickyDistance || $elem[0].offsetHeight, setSticky = function () { !$elem.hasClass(className) && $elem.addClass(className).css('width', stickyWidth) && (typeof opts.onSticky == 'function' && opts.onSticky($elem, $target)); return true; }, states = { staticSticky: function () { setSticky() && $elem.css(opts.type, opts.stickyOffset); }, dynamicSticky: function (rect) { setSticky() && $elem.css(opts.type, rules[opts.type].getDynamicOffset(rect)); }, unSticky: function () { $elem.hasClass(className) && $elem.removeClass(className).css('width', '').css(opts.type, '') && (typeof opts.onUnSticky == 'function' && opts.onUnSticky($elem, $target)); } }, rules = { top: { getState: function (rect) { if (rect.top < 0 && (rect.bottom - unStickyDistance) > 0) return 'staticSticky'; else if ((rect.bottom - unStickyDistance) <= 0 && rect.bottom > 0) return 'dynamicSticky'; else return 'unSticky'; }, getDynamicOffset: function (rect) { return -(unStickyDistance - rect.bottom); } }, bottom: { getState: function (rect) { if (rect.bottom > docClientHeight && (rect.top + unStickyDistance) < docClientHeight) return 'staticSticky'; else if ((rect.top + unStickyDistance) >= docClientHeight && rect.top < docClientHeight) return 'dynamicSticky'; else return 'unSticky'; }, getDynamicOffset: function (rect) { return -(unStickyDistance + rect.top - docClientHeight); } } }, className = 'sticky--in-' + opts.type, $win = $(window); setStickyWidth(); $win.scroll(throttle(sticky, opts.wait)); !opts.isFixedWidth && $win.resize(throttle(function () { setStickyWidth(); $elem.hasClass(className) && $elem.css('width', stickyWidth); sticky(); }, opts.wait)); $win.resize(throttle(function () { docClientHeight = document.documentElement.clientHeight; }, opts.wait)); function sticky() { var rect = $target[0].getBoundingClientRect(), curState = rules[opts.type].getState(rect); states[curState](rect); } } })(jQuery);
Ce qui est difficile à comprendre est peut-être la logique de la méthode getState. Certaines des idées de cette partie sont expliquées plus en détail dans le blog précédent.
3. Instructions d'application dans la barre latérale du blog
Tout d'abord, vous devez coller cette implémentation dans le champ de texte HTML du pied de page des paramètres du blog, puis ajouter le code suivant pour initialiser :
var timer = setInterval(function(){ if($('#blogCalendar').length && $('#profile_block').length && $('#sidebar_search').length) { new Sticky('#sideBar', { target: '#main', onSticky: function($elem, $target){ $target.css('min-height',$elem.outerHeight()); $elem.css('left', '65px'); }, onUnSticky: function($elem, $target){ $target.css('min-height',''); $elem.css('left', ''); } }); } },100);
Timer est utilisé car le contenu de la barre latérale est chargé par ajax, et il est impossible d'ajouter des rappels lors de ces requêtes ajax. Vous ne pouvez juger si la barre latérale est chargée que par le contenu qu'elles renvoient.
4.Résumé
Ce week-end, j'ai réfléchi à la façon d'améliorer le composant collant. De plus, j'ai passé la majeure partie de la journée à écrire cet article. Au moins maintenant, je me sens un peu satisfait de la fonction et de la mise en œuvre du composant collant que j'ai fini d'écrire en dernier. C'était bizarre, comme s'il manquait quelque chose, mais il s'est avéré que c'était parce qu'il manquait encore tellement de choses. À l'heure actuelle, ce composant ne peut avoir pour effet que de réparer et de défaire. Pour les travaux pratiques, l'effet de ce niveau peut ne pas être suffisant. Les fonctions courantes sur Internet qui prennent en charge le défilement de la navigation ou la navigation par onglets tout en étant corrigées sont également très courantes. Suivant Cet article présentera le composant sticky basé sur cet article, comment implémenter les composants navScrollSticky et tabSticky, alors restez à l'écoute.
Merci d'avoir lu :)
Explication supplémentaire :
Dans IE et Firefox, lors de l'actualisation de la page, si la page défile avant l'actualisation, l'opération d'actualisation définira la position de défilement de la page sur la position d'actualisation, mais l'événement de défilement ne sera pas déclenché, il doit donc être appelé immédiatement après le composant est initialisé. Une fonction collante :