Agregar un efecto de diapositiva al menú desplegable de bootstrap


80

Estoy usando bootstrap y me gustaría agregar animación a un menú desplegable. Quiero agregarle una animación, deslizarme hacia abajo y hacia arriba cuando lo deje. ¿Cómo podría hacer esto?

Cosas que probé:

Cambiar el archivo desplegable Js de esta manera:

¿Cómo puedo hacer que el menú desplegable de navegación de Bootstrap se deslice suavemente hacia arriba y hacia abajo?


1
¿Incluiste bootstrap-dropdown.js en alguna parte? No lo veo en el encabezado .. y awwww estos pasteles. ¡Tengo tanta hambre ahora!
ablm

Hmm, está dentro de boostrap.js, lo incluí pero no funcionó, así que lo eliminé pero lo volví a agregar y sí, si elimino el mini, no funcionará en absoluto
Ben Beri

EDITAR: oh sí, está incluido.
ablm

Umm. El menú desplegable predeterminado no se desliza; En la versión modificada (de los enlaces), hay un $ el.parent (). SlideToggle (); que llama a una animación JQuery.
ablm

Y por cierto, no veo por qué incluyes bootstrap 2 veces. bootstrap.min.js es la versión reducida.
ablm

Respuestas:


149

Si actualiza a Bootstrap 3 (BS3), han expuesto muchos eventos de Javascript que son agradables para vincular su funcionalidad deseada. En BS3, este código le dará a todos sus menús desplegables el efecto de animación que está buscando:

  // Add slideDown animation to Bootstrap dropdown when expanding.
  $('.dropdown').on('show.bs.dropdown', function() {
    $(this).find('.dropdown-menu').first().stop(true, true).slideDown();
  });

  // Add slideUp animation to Bootstrap dropdown when collapsing.
  $('.dropdown').on('hide.bs.dropdown', function() {
    $(this).find('.dropdown-menu').first().stop(true, true).slideUp();
  });

Puede leer acerca de los eventos BS3 aquí y específicamente sobre los eventos desplegables aquí .


4
Tengo un problema con la parte de slideUp de esto. En el modo contraído (Chrome), el ancho del menú desplegable vuelve al ancho como si se estuviera viendo en pantalla completa. Mientras usa FF, se desliza hacia abajo, pero se esconde en lugar de deslizarse hacia arriba.
ScubaSteve

3
Yo también veo este problema, de hecho creé otro hilo preguntando específicamente sobre él ... stackoverflow.com/questions/26267207/… pero no sé exactamente cómo solucionarlo. Siento que la clase Dropdown no está esperando a que finalice la transición antes de procesar el hidden.bs.dropdownevento.
Kyle Johnson

3
esto funciona y hace lo que deseo, pero en la ventana de visualización móvil, el menú desplegable de los submenús desaparece rápidamente y se ve entrecortado, ¡gracias por compartir!
Tío Iroh

No puedo hacer que esto funcione. Sigo recibiendo el siguiente error: "Uncaught TypeError: $ (...). Find (...). First (...). Stop is not a function". No entiendo cómo .stop () no es una función a menos que la animación no haya comenzado de alguna manera cuando se activa el evento. ¿Qué me estoy perdiendo?
Juan

Vea la respuesta de Slipoch más abajo para una solución móvil.
Tashows

71

También es posible evitar el uso de JavaScript para el efecto desplegable y usar la transición CSS3, agregando este pequeño fragmento de código a su estilo:

.dropdown .dropdown-menu {
    -webkit-transition: all 0.3s;
    -moz-transition: all 0.3s;
    -ms-transition: all 0.3s;
    -o-transition: all 0.3s;
    transition: all 0.3s;

    max-height: 0;
    display: block;
    overflow: hidden;
    opacity: 0;
}

.dropdown.open .dropdown-menu { /* For Bootstrap 4, use .dropdown.show instead of .dropdown.open */
    max-height: 300px;
    opacity: 1;
}

El único problema con esta forma es que debe especificar manualmente la altura máxima. Si establece un valor muy grande, su animación será muy rápida.

Funciona de maravilla si conoce la altura aproximada de sus menús desplegables; de lo contrario, aún puede usar javascript para establecer un valor preciso de altura máxima.

Aquí hay un pequeño ejemplo: DEMO


! Hay un pequeño error con el relleno en esta solución, verifique el comentario de Jacob Stamm con la solución.


2
Buena solución, lo agradezco.
Lalit Narayan Mishra

Me encanta esto, pero desafortunadamente la animación deslizante solo funciona una vez. Si cierro el menú y lo vuelvo a abrir sin actualizar la página, se abre instantáneamente. ¿Alguién mas está teniendo este problema?
Nathan R

8
Gran solucion Desafortunadamente, hay un pequeño error. Aún se puede hacer clic en el primer elemento del menú desplegable, incluso cuando está contraído. Puede saberlo colocando el cursor ligeramente debajo del menú desplegable contraído. No es gran cosa en este caso, porque su primera opción es texto sin formato. Sin embargo, cuando es un enlace, es un problema. Mi solución es "animar" la visibilidad también, pero retrasarlo después de que las otras cosas terminen de animarse. Échale un vistazo: jsfiddle.net/stamminator/kjLe1tfs/1
Jacob Stamm

Gracias por señalar ese error, Jacob. Parece que esto es un problema con el relleno en la lista. Si no le importa, agregaré su solución para responder.
MadisonTrash

1
También se podría agregar pointer-events:nonea la versión contraída, luego agregar point-events: allal menú una vez que lo haya hecho.show
Gray Ayer

24

Estoy haciendo algo así, pero al pasar el mouse en lugar de hacer clic. Este es el código que estoy usando, es posible que pueda modificarlo un poco para que funcione al hacer clic

$('.navbar .dropdown').hover(function() {
  $(this).find('.dropdown-menu').first().stop(true, true).delay(250).slideDown();
}, function() {
  $(this).find('.dropdown-menu').first().stop(true, true).delay(100).slideUp()
});

¡Esto funciona muy bien! Pero, ¿hay alguna forma de trabajar con él al pasar el mouse en lugar de hacer clic?
arefin2k

El código que publiqué arriba está al pasar el mouse y no al hacer clic.
Yohn

¡Buena! Gracias.
ilmk

No funciona para mí. <script>
Agregué

16

No sé si puedo superar este hilo, pero descubrí una solución rápida para el error visual que ocurre cuando la clase abierta se elimina demasiado rápido. Básicamente, todo lo que hay que hacer es agregar una función OnComplete dentro del evento slideUp y restablecer todas las clases y atributos activos. Va algo como esto:

Aquí está el resultado: Ejemplo de Bootply

Javascript / Jquery:

$(function(){
    // ADD SLIDEDOWN ANIMATION TO DROPDOWN //
    $('.dropdown').on('show.bs.dropdown', function(e){
        $(this).find('.dropdown-menu').first().stop(true, true).slideDown();
    });

    // ADD SLIDEUP ANIMATION TO DROPDOWN //
    $('.dropdown').on('hide.bs.dropdown', function(e){
        e.preventDefault();
        $(this).find('.dropdown-menu').first().stop(true, true).slideUp(400, function(){
            //On Complete, we reset all active dropdown classes and attributes
            //This fixes the visual bug associated with the open class being removed too fast
            $('.dropdown').removeClass('show');
            $('.dropdown-menu').removeClass('show');
            $('.dropdown').find('.dropdown-toggle').attr('aria-expanded','false');
        });
    });
});

La demostración tiene un menú que se atasca al abrir después de hacer clic en algunos seguidos.
SventoryMang

@DOTang sí, supongo que no fue perfecto, de ahí la solución rápida, supongo que una pequeña refactorización lo mejoraría. Con suerte, para cuando salga Bootstrap4, lo habrán arreglado
IndieRok

11

aquí está mi solución para el efecto de deslizamiento y desvanecimiento:

   // Add slideup & fadein animation to dropdown
   $('.dropdown').on('show.bs.dropdown', function(e){
      var $dropdown = $(this).find('.dropdown-menu');
      var orig_margin_top = parseInt($dropdown.css('margin-top'));
      $dropdown.css({'margin-top': (orig_margin_top + 10) + 'px', opacity: 0}).animate({'margin-top': orig_margin_top + 'px', opacity: 1}, 300, function(){
         $(this).css({'margin-top':''});
      });
   });
   // Add slidedown & fadeout animation to dropdown
   $('.dropdown').on('hide.bs.dropdown', function(e){
      var $dropdown = $(this).find('.dropdown-menu');
      var orig_margin_top = parseInt($dropdown.css('margin-top'));
      $dropdown.css({'margin-top': orig_margin_top + 'px', opacity: 1, display: 'block'}).animate({'margin-top': (orig_margin_top + 10) + 'px', opacity: 0}, 300, function(){
         $(this).css({'margin-top':'', display:''});
      });
   });

Gracias. Tu código funciona perfectamente. Sin embargo, no entiendo por qué necesitamos tener $(this).css({'margin-top':''});@Vedmant
DucCuong

Esto es para eliminar el parámetro margin-top después de que se complete la animación, en caso de que los menús desplegables tengan algunos márgenes agregados en estilos personalizados (como en mi tema) .Tiene que funcionar sin él, ya que se anima a orig_margin_top back, pero es más limpio no dejar más estilos en línea una vez completada la animación.
Vedmant

9

Actualización 2018 Bootstrap 4

En Boostrap 4, la .openclase ha sido reemplazada por .show. Quería implementar esto usando solo transiciones CSS sin la necesidad de JS o jQuery adicional ...

.show > .dropdown-menu {
  max-height: 900px;
  visibility: visible;
}

.dropdown-menu {
  display: block;
  max-height: 0;
  visibility: hidden;
  transition: all 0.5s ease-in-out;
  overflow: hidden;
}

Demostración: https://www.codeply.com/go/3i8LzYVfMF

Nota: max-heightse puede establecer en cualquier valor grande que sea suficiente para acomodar el contenido desplegable.


Esto funciona perfectamente. Para mí, ni siquiera necesitaba especificar la altura máxima ya que estaba tratando de lograr un efecto diferente usando margin-top.
Supreme Dolphin

me gusta esto, solo necesitas css sin un montón de códigos js ..
aswzen

8

Al hacer clic, se puede hacer usando el siguiente código

$('.dropdown-toggle').click(function() {
  $(this).next('.dropdown-menu').slideToggle(500);
});

Esto funciona bien, pero cuando pierde el enfoque, el menú desplegable permanece abierto, lo cual no es normal.
Barry Michael Doyle

7

Estoy usando el código anterior, pero he cambiado el efecto de retardo mediante slideToggle.

Desliza el menú desplegable al pasar el mouse con animación.

$('.navbar .dropdown').hover(function() {
    $(this).find('.dropdown-menu').first().stop(true, true).slideToggle(400);
    }, function() {
    $(this).find('.dropdown-menu').first().stop(true, true).slideToggle(400)
    });

1
realmente no es necesario definirlo dos veces. cuando se usa un conmutador, funcionará sin el segundo.
Dallas

3

Respuesta ampliada, fue mi primera respuesta, así que disculpe si no había suficientes detalles antes.

Para Bootstrap 3.x, personalmente prefiero las animaciones CSS y he estado usando animate.css y junto con Bootstrap Dropdown Javascript Hooks. Aunque puede que no tenga exactamente el efecto que busca, es un enfoque bastante flexible.

Paso 1: agregue animate.css a su página con las etiquetas de encabezado:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.4.0/animate.min.css">

Paso 2: use el HTML Bootstrap estándar en el disparador:

<div class="dropdown">
  <button type="button" data-toggle="dropdown">Dropdown trigger</button>

  <ul class="dropdown-menu">
    ...
  </ul>
</div>

Paso 3: luego agregue 2 atributos de datos personalizados al elemento del menú desplegable; menú desplegable de datos para la animación de entrada y menú desplegable de datos para la animación de salida. Estos pueden ser efectos de animate.css como fadeIn o fadeOut

<ul class="dropdown-menu" data-dropdown-in="fadeIn" data-dropdown-out="fadeOut">
......
</ul>

Paso 4: A continuación, agregue el siguiente Javascript para leer los atributos de datos de entrada / salida del menú desplegable de datos y reaccionar a los eventos / ganchos de la API de JavaScript de Bootstrap ( http://getbootstrap.com/javascript/#dropdowns-events ):

var dropdownSelectors = $('.dropdown, .dropup');

// Custom function to read dropdown data
// =========================
function dropdownEffectData(target) {
  // @todo - page level global?
  var effectInDefault = null,
      effectOutDefault = null;
  var dropdown = $(target),
      dropdownMenu = $('.dropdown-menu', target);
  var parentUl = dropdown.parents('ul.nav'); 

  // If parent is ul.nav allow global effect settings
  if (parentUl.size() > 0) {
    effectInDefault = parentUl.data('dropdown-in') || null;
    effectOutDefault = parentUl.data('dropdown-out') || null;
  }

  return {
    target:       target,
    dropdown:     dropdown,
    dropdownMenu: dropdownMenu,
    effectIn:     dropdownMenu.data('dropdown-in') || effectInDefault,
    effectOut:    dropdownMenu.data('dropdown-out') || effectOutDefault,  
  };
}

// Custom function to start effect (in or out)
// =========================
function dropdownEffectStart(data, effectToStart) {
  if (effectToStart) {
    data.dropdown.addClass('dropdown-animating');
    data.dropdownMenu.addClass('animated');
    data.dropdownMenu.addClass(effectToStart);    
  }
}

// Custom function to read when animation is over
// =========================
function dropdownEffectEnd(data, callbackFunc) {
  var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
  data.dropdown.one(animationEnd, function() {
    data.dropdown.removeClass('dropdown-animating');
    data.dropdownMenu.removeClass('animated');
    data.dropdownMenu.removeClass(data.effectIn);
    data.dropdownMenu.removeClass(data.effectOut);

    // Custom callback option, used to remove open class in out effect
    if(typeof callbackFunc == 'function'){
      callbackFunc();
    }
  });
}

// Bootstrap API hooks
// =========================
dropdownSelectors.on({
  "show.bs.dropdown": function () {
    // On show, start in effect
    var dropdown = dropdownEffectData(this);
    dropdownEffectStart(dropdown, dropdown.effectIn);
  },
  "shown.bs.dropdown": function () {
    // On shown, remove in effect once complete
    var dropdown = dropdownEffectData(this);
    if (dropdown.effectIn && dropdown.effectOut) {
      dropdownEffectEnd(dropdown, function() {}); 
    }
  },  
  "hide.bs.dropdown":  function(e) {
    // On hide, start out effect
    var dropdown = dropdownEffectData(this);
    if (dropdown.effectOut) {
      e.preventDefault();   
      dropdownEffectStart(dropdown, dropdown.effectOut);   
      dropdownEffectEnd(dropdown, function() {
        dropdown.dropdown.removeClass('open');
      }); 
    }    
  }, 
});

Paso 5 (opcional): si desea acelerar o alterar la animación, puede hacerlo con CSS como el siguiente:

.dropdown-menu.animated {
  /* Speed up animations */
  -webkit-animation-duration: 0.55s;
  animation-duration: 0.55s;
  -webkit-animation-timing-function: ease;
  animation-timing-function: ease;
}

Escribió un artículo con más detalles y una descarga si alguien está interesado: artículo: http://bootbites.com/tutorials/bootstrap-dropdown-effects-animatecss

Espero que sea útil y que este segundo artículo tenga el nivel de detalle que se necesita Tom


3
Sería bueno que al menos incluyeras algunos detalles en tu respuesta; solo vincular a tu sitio no es realmente una buena respuesta.
ajshort

@GeraldSchneider actualizó la respuesta con más detalles. Fue mi primera respuesta sobre stackoverflow, así que agradezco los comentarios.
Tom James

2
$('.navbar .dropdown').hover(function() {
    $(this).find('.dropdown-menu').first().stop(true, true).slideDown();
}, function() {
    $(this).find('.dropdown-menu').first().stop(true, true).slideUp();
});

Este código funciona si desea mostrar menús desplegables al pasar el mouse.

Acabo de cambiar .slideTogglea .slideDown& .slideUp, y eliminé el (400)tiempo


1

Aquí hay una solución simple y agradable jQueryque funciona muy bien:

$('.dropdown-toggle').click(function () {
    $(this).next('.dropdown-menu').slideToggle(300);
});

$('.dropdown-toggle').focusout(function () {
    $(this).next('.dropdown-menu').slideUp(300);
})

La alternancia de la animación de diapositivas se produce al hacer clic y siempre se desliza hacia arriba al perder el enfoque.

Cambie el 300valor a lo que desee, cuanto menor sea el número, más rápida será la animación.

Editar:

Esta solución solo funcionará para vistas de escritorio. Necesitará algunas modificaciones adicionales para que se muestre bien en dispositivos móviles.


Esto funciona para mí, pero después de mover el mouse al submenú (es decir, lejos de dropdown-toggle), desaparece nuevamente, lo que significa que no puedo seleccionar los elementos del submenú
Bassie

1

REFERENCIA DE BOOTSTRAP 3

Agregado porque sigo siendo atrapado por la solución en este hilo y me llena todo el tiempo.

Básicamente, el menú desplegable BS elimina inmediatamente la .openclase del padre, por lo que deslizarse hacia arriba no funciona.

Utilice el mismo bit que otras soluciones para slideDown ();

// ADD SLIDEUP ANIMATION TO DROPDOWN //
$('.dropdown').on('hide.bs.dropdown', function(e){
  e.preventDefault();
  $(this).find('.dropdown-menu').first().stop(true, true).slideUp(300, function(){
    $(this).parent().removeClass('open');
  });
});

0

Para Bootstrap 3, esta variación de las respuestas anteriores hace que la slideUp()animación móvil sea más fluida; las respuestas anteriores tienen una animación entrecortada porque Bootstrap elimina la .openclase del padre del conmutador inmediatamente, por lo que este código restaura la clase hasta que slideUp()finaliza la animación.

// Add animations to topnav dropdowns
// based on https://stackoverflow.com/a/19339162
// and https://stackoverflow.com/a/52231970

$('.dropdown')
  .on('show.bs.dropdown', function() {
    $(this).find('.dropdown-menu').first().stop(true, true).slideDown(300);
  })
  .on('hide.bs.dropdown', function() {
    $(this).find('.dropdown-menu').first().stop(true, false).slideUp(300, function() {
      $(this).parent().removeClass('open');
    });
  })
  .on('hidden.bs.dropdown', function() {
    $(this).addClass('open');
  });

Diferencias clave:

  • En el hide.bs.dropdowncontrolador de eventos estoy usando .stop()el valor predeterminado ( false) para su segundo argumento ( jumpToEnd)
  • El hidden.bs.dropdowncontrolador de eventos restaura la .openclase al padre del conmutador desplegable, y lo hace prácticamente inmediatamente después de que la clase se haya eliminado por primera vez. Mientras tanto, la slideUp()animación todavía se está ejecutando, y al igual que en las respuestas anteriores, su devolución de llamada "the-animation-is-complete" es responsable de eliminar finalmente la .openclase de su padre.
  • Los métodos están encadenados porque el selector para cada controlador de eventos es el mismo

0

Intro

En el momento de redactar este informe, la respuesta original tiene ahora 8 años. Aún así, siento que aún no hay una solución adecuada a la pregunta original.

Bootstrap ha recorrido un largo camino desde entonces y ahora está en 4.5.2 . Esta respuesta aborda esta misma versión.

El problema con todas las demás soluciones hasta ahora

El problema con todas las demás respuestas es que, si bien se conectan a show.bs.dropdown/ hide.bs.dropdown, los eventos de seguimiento shown.bs.dropdown/ hidden.bs.dropdownse activan demasiado pronto (la animación aún está en curso) o no se activan en absoluto porque se suprimieron ( e.preventDefault()).

Una solucion limpia

Dado que la implementación de show()y hide()en la Dropdownclase Bootstraps comparte algunas similitudes, las he agrupado toggleDropdownWithAnimation()al imitar el comportamiento original y agregué pequeñas funciones de ayuda de QoL a showDropdownWithAnimation()y hideDropdownWithAnimation().
toggleDropdownWithAnimation()crea un evento shown.bs.dropdown/ de hidden.bs.dropdownla misma manera que Bootstrap lo hace. Este evento se activa luego de que se completa la animación, tal como era de esperar.

/**
 * Toggle visibility of a dropdown with slideDown / slideUp animation.
 * @param {JQuery} $containerElement The outer dropdown container. This is the element with the .dropdown class.
 * @param {boolean} show Show (true) or hide (false) the dropdown menu.
 * @param {number} duration Duration of the animation in milliseconds
 */
function toggleDropdownWithAnimation($containerElement, show, duration = 300): void {
    // get the element that triggered the initial event
    const $toggleElement = $containerElement.find('.dropdown-toggle');
    // get the associated menu
    const $dropdownMenu = $containerElement.find('.dropdown-menu');
    // build jquery event for when the element has been completely shown
    const eventArgs = {relatedTarget: $toggleElement};
    const eventType = show ? 'shown' : 'hidden';
    const eventName = `${eventType}.bs.dropdown`;
    const jQueryEvent = $.Event(eventName, eventArgs);

    if (show) {
        // mimic bootstraps element manipulation
        $containerElement.addClass('show');
        $dropdownMenu.addClass('show');
        $toggleElement.attr('aria-expanded', 'true');
        // put focus on initial trigger element
        $toggleElement.trigger('focus');
        // start intended animation
        $dropdownMenu
            .stop() // stop any ongoing animation
            .hide() // hide element to fix initial state of element for slide down animation
            .slideDown(duration, () => {
            // fire 'shown' event
            $($toggleElement).trigger(jQueryEvent);
        });
    }
    else {
        // mimic bootstraps element manipulation
        $containerElement.removeClass('show');
        $dropdownMenu.removeClass('show');
        $toggleElement.attr('aria-expanded', 'false');
        // start intended animation
        $dropdownMenu
            .stop() // stop any ongoing animation
            .show() // show element to fix initial state of element for slide up animation
            .slideUp(duration, () => {

            // fire 'hidden' event
            $($toggleElement).trigger(jQueryEvent);
        });
    }
}

/**
 * Show a dropdown with slideDown animation.
 * @param {JQuery} $containerElement The outer dropdown container. This is the element with the .dropdown class.
 * @param {number} duration Duration of the animation in milliseconds
 */
function showDropdownWithAnimation($containerElement, duration = 300) {
    toggleDropdownWithAnimation($containerElement, true, duration);
}

/**
 * Hide a dropdown with a slideUp animation.
 * @param {JQuery} $containerElement The outer dropdown container. This is the element with the .dropdown class.
 * @param {number} duration Duration of the animation in milliseconds
 */
function hideDropdownWithAnimation($containerElement, duration = 300) {
    toggleDropdownWithAnimation($containerElement, false, duration);
}

Vincular oyentes de eventos

Ahora que hemos escrito las devoluciones de llamada adecuadas para mostrar / ocultar un menú desplegable con una animación, vamos a vincularlas a los eventos correctos.

Un error común que he visto mucho en otras respuestas es vincular a los oyentes de eventos a los elementos directamente. Si bien esto funciona bien para los elementos DOM presentes en el momento en que se registra el detector de eventos, no se vincula a los elementos agregados más adelante.

Es por eso que generalmente es mejor vincularse directamente a document:

$(function () {

    /* Hook into the show event of a bootstrap dropdown */
    $(document).on('show.bs.dropdown', '.dropdown', function (e) {
        // prevent bootstrap from executing their event listener
        e.preventDefault();
        
        showDropdownWithAnimation($(this));
    });

    /* Hook into the hide event of a bootstrap dropdown */
    $(document).on('hide.bs.dropdown', '.dropdown', function (e) {
        // prevent bootstrap from executing their event listener
        e.preventDefault();
          
        hideDropdownWithAnimation($(this));
    });

});
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.