Dans ces dernières versions la modal de BS permet justement de s'empiler mais malheureusement il y a un effet, que je trouve désagréable, c'est qu'a chaque ouverture d'une fenêtre un élément "backdrop" viens s'ajouter au DOM, rendant l'arrière plan de plus en plus opaque au fur et à mesure de l'ouverture de fenêtre.
Je me suis donc penché sur les solutions permettant de minimiser l'effet et deux solutions s'offrent à nous:
- Soit nous définissons un code "rustine" qui permettra de corriger le problème.
- Soit nous agissons directement au niveau du plugin.
La première solution est normalement celle que tout le monde devrait choisir, en effet les modals BS offrent une API permettant d'agir sur les éléments à 4 étapes de l'initialisation et de la destruction des fenêtres. De plus agir de cette manière nous assure l'intégrité du framework BS notamment lorsque décision est prise de le mettre à jour.
Malheureusement cette solution souffre d'un problème de "timing" dans notre cas d'étude, en agissant de cette manière et avec le même code que je propose ci dessous, vous constaterez des effets visuels indésirables.
C'est pourquoi j'ai opté pour la deuxième solution, un peu plus contraignante mais très fonctionnelle:
Après avoir lut le code du plugin deux fonctions nous intéressent:
Modal.prototype.show et Modal.prototype.backdrop.
Fonction Show:
Code javascript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | Modal.prototype.show = function (_relatedTarget) { var that = this var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) this.$element.trigger(e) if (this.isShown || e.isDefaultPrevented()) return this.isShown = true this.checkScrollbar() this.setScrollbar() this.$body .addClass('modal-open') // Nous intervenons ici afin d'enregistrer un nombre de modals ouvertes .data('modalIndice', this.$body.data('modalIndice') ? this.$body.data('modalIndice') + 1 : 1 ) this.escape() this.resize() this.$element .on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) // Grace à la donnée modalIndice nous pouvons attribuer un numéro ordonné à la modal actuelle. .data('modalOrder', this.$body.data('modalIndice')) this.$dialog.on('mousedown.dismiss.bs.modal', function () { that.$element.one('mouseup.dismiss.bs.modal', function (e) { if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true }) }) this.backdrop(function () { var transition = $.support.transition && that.$element.hasClass('fade') if (!that.$element.parent().length) { that.$element.appendTo(that.$body) // don't move modals dom position } that.$element .show() .scrollTop(0) that.adjustDialog() if (transition) { that.$element[0].offsetWidth // force reflow } that.$element .addClass('in') .attr('aria-hidden', false) that.enforceFocus() var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) transition ? that.$dialog // wait for modal to slide in .one('bsTransitionEnd', function () { that.$element.trigger('focus').trigger(e) }) .emulateTransitionEnd(Modal.TRANSITION_DURATION) : that.$element.trigger('focus').trigger(e) }) } |
Fonction Backdrop:
Code javascript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | Modal.prototype.backdrop = function (callback) { var that = this var animate = this.$element.hasClass('fade') ? 'fade' : '' var lastVisibleBackdrop = this.$body.find('.modal-backdrop:last:visible') // Nous récupérons le dernier arrière plan visible var lastHiddenBackdrop = this.$body.find('.modal-backdrop:hidden:last') // Nous récupérons le dernier arrière plan caché if (this.isShown && this.options.backdrop) { var doAnimate = $.support.transition && animate // On cache tous les autres backdrop à part celui actif puis on réduis le z-index des autres modals pour les faire passer sous le backdrop actif if (lastVisibleBackdrop.length) { lastVisibleBackdrop.hide(); this.$body.find('.modal:visible').each(function() { if ($(this)[0] !== that.$element[0]) { $(this).css('z-index', 1030) } }) } this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') .appendTo(this.$body) this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { if (this.ignoreBackdropClick) { this.ignoreBackdropClick = false return } if (e.target !== e.currentTarget) return this.options.backdrop == 'static' ? this.$element[0].focus() : this.hide() }, this)) if (doAnimate) this.$backdrop[0].offsetWidth // force reflow this.$backdrop.addClass('in') if (!callback) return doAnimate ? this.$backdrop .one('bsTransitionEnd', callback) .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : callback() } else if (!this.isShown && this.$backdrop) { this.$backdrop.removeClass('in') // On affiche le dernier backdrop caché puis on augmente le z-index de la modal ayant l'indice juste au dessous de celle qui est active et en cours de destruction if (lastHiddenBackdrop.length) { lastHiddenBackdrop.show(); this.$body.find('.modal:visible').each(function() { if ($(this).data('modalOrder') === that.$element.data('modalOrder') - 1) { $(this).css('z-index', 1050) } }) } var callbackRemove = function () { that.removeBackdrop() callback && callback() } $.support.transition && this.$element.hasClass('fade') ? this.$backdrop .one('bsTransitionEnd', callbackRemove) .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : callbackRemove() } else if (callback) { callback() } } |
Et le tour est joué . Certes il reste encore pas mal de travail afin de fournir un plugin plus fournis que l'original et ainsi s'en détaché, mais l'idée est là et elle n'est pas bien compliqué.
Avis, remarques et optimisations sont les bienvenus
Le code est disponible ici