New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If content is hidden during Slick bootstrap, Slick refuses to play ball #187

Closed
alexrussell opened this Issue Apr 28, 2014 · 58 comments

Comments

Projects
None yet
@alexrussell

alexrussell commented Apr 28, 2014

I'm not sure that this is easily fixable but I'm implementing Slick on a Bootstrap website and I have a Slick-based gallery in a set of Bootstrap tabs. If the gallery tab is the active one on page load, the Slick carousel works fine, but if the gallery tab is not the one that's active on page load the Slick carousel seems to break (the tab basically looks empty).

At a guess this is due to the items being hidden as Slick is set up against the items and so Slick can't calculate the dimensions of the slides, etc.

A possible non-Slick-based fix is to hook into BS tab event system and only call $.fx.slick() when the tab is first switched to. Of course this only works for JavaScript components that allow a developer to hook into this kind of event system, so maybe there's a way for Slick to get round this limitation without requiring developers to do these hackish bits of code (which they sometimes may not be able to do).

@kenwheeler

This comment has been minimized.

Owner

kenwheeler commented Apr 28, 2014

Sounds like because it is "display: none;" that it can't get the dimensions. I would try manually firing a window resize event on tab change, and see if that gives it a kick in the ass.

@alexrussell

This comment has been minimized.

alexrussell commented Apr 28, 2014

Looking into the source, there are kinda a few places where widths are calculated and passed around, but it all seems to boil down to the slick-list element that is injected: on line 1143 its width is calculated and used in a further calculation of the width of a slide. From those values of listWidth and slideWidth all other widths are calculated. Maybe something can be done at this point to make the element 'positioned' and thus able to have its width calculated.

@alexrussell

This comment has been minimized.

alexrussell commented Apr 28, 2014

@kenwheeler the resize thing seems to fix it. As I said in my original issue text this does rely on a developer's ability ot actually hook into these events, so it'd still be good to see if Slick could do its own workaround.

The code I used in the end was:

// set up Slick carousel
$('.gallery').has('img + img').slick({
    autoplaySpeed: 5e3,
    arrows: false,
    infinite: true,
    slide: 'img',
    slidesToScroll: 1,
    slidesToShow: 1,
    speed: 300,
    touchMove: false,
});

// hook into Bootstrap shown event and manually trigger 'resize' event so that Slick recalculates the widths
$('[href="#gallery"]').on('shown.bs.tab', function (e) {
    $('.gallery').resize();
});
@kenwheeler

This comment has been minimized.

Owner

kenwheeler commented Apr 28, 2014

You can manually refresh slick itself using slickSetOption(anyoption, anyvalue, true);

@kenwheeler kenwheeler closed this Apr 28, 2014

@alexrussell

This comment has been minimized.

alexrussell commented Apr 29, 2014

Still doesn't really fix the underlying problem, but fair enough if you think this isn't something that can really be done by slick itself.

@kenwheeler

This comment has been minimized.

Owner

kenwheeler commented Apr 29, 2014

Any suggestions on how?

@kenwheeler

This comment has been minimized.

Owner

kenwheeler commented Apr 29, 2014

The underlying problem is essentially a javascript/dom problem, that you can't get the dimensions of hidden elements.

@alexrussell

This comment has been minimized.

alexrussell commented Apr 29, 2014

Well I know that, for example, various JS libraries (Prototype and jQuery) do try their best to make this happen by quickly making an element displayed (i.e. not display: none) but invisible (i.e. opacity: 0 or visibility: hidden) to temporarily calculate the width/height of things, before them resetting this. However, I think the main issue is that it could be that a parent of the element in question that is not displayed rather than the element itself, that required more work to do. It's still technically possible, but yeah maybe it's too much to ask of a widget like slick versus a library like jQuery, etc. (I wonder if you can somehow subscribe to a 'visibility changed' event on an element. I haven't heard of such things, but that'd be a perfect fix.)

I guess the dependency on jQuery to do all of this DOM stuff suggests it's more an upstream issue than a slick issue. The problem is that jQuery doesn't seem to be able to return the width of an element whose parent is set to display: none.

Still, it may be worth mentioning this as a caveat somewhere in the docs - just mention that obviously if the element is hidden when you call .slick() on it, slick will incorrectly calculate its sizes, and slick must be refreshed when the element is finally displayed. Or if slick detects the width as 0, it puts a message in the console suggesting the developer do something about this. I dunno.

@kenwheeler

This comment has been minimized.

Owner

kenwheeler commented Apr 29, 2014

It's not a jQuery issue, its a browser/DOM issue. You can't get the dimensions of a hidden element. As the implementor, if you are hiding slick, be sure to hide it after it has initialized, or mitigate the dimension calculation by triggering a resize event.

@richard-keller

This comment has been minimized.

richard-keller commented May 3, 2014

I think it might be worth putting this somewhere in the docs. I've also just run into this problem while using Bootstrap 3 tabs (when the carousel is placed in a tab that is initially hidden), and it wasn't immediately clear how to fix the issue. As already mentioned, calling resize() fixes the problem.

@kenwheeler

This comment has been minimized.

Owner

kenwheeler commented May 3, 2014

Yeah I think it's high time I write up a FAQ section to clear up certain things that implementors may encounter,
But that don't merit a fix/inclusion to the core library itself.

@patrickvaillancourt

This comment has been minimized.

patrickvaillancourt commented Oct 14, 2014

The .resize() option didn't work for me. I experienced a similar problem attempting to render an embedded google map inside a bootstrap tab as well. To my surprise both problems were resolved by changing how bootstrap handles hidden tabs:

/* bootstrap hack: fix content width inside hidden tabs */
.tab-content > .tab-pane, .pill-content > .pill-pane {
    display: block;    /* undo display:none          */
    height: 0;         /* height:0 is also invisible */ 
    overflow: hidden;  /* no-overflow                */
}
.tab-content > .active, .pill-content > .active {
    height: auto;      /* let the content decide it  */
} /* bootstrap hack end */

more here: https://groups.google.com/forum/#!topic/twitter-bootstrap-stackoverflow/0Aqzs5s_hEY

@JensNielsen81

This comment has been minimized.

JensNielsen81 commented Oct 23, 2014

This was also driving me crazy...
I´m using jquery UI tabs, where sliders in inactive tabs were not being formatet correctly.
I fixed it with a little help from @patrickvaillancourt css solution.

This is what i did:

.ui-widget-content[aria-hidden="true"] {
    display: block !important;
    height: 0px;
    overflow: hidden;
}
.ui-widget-content[aria-hidden="false"] {
    display: block !important;
    height: auto;
}

Not sure if this is the right solution, but it worked for me, and it might help others, seeing this is the place you land, when you google the problem :)

@kenwheeler

This comment has been minimized.

Owner

kenwheeler commented Oct 23, 2014

@JensNielsen81 Yeah its tough because if you are using display: none, slick can't get the correct dimensions from a hidden element. I'm certainly not going to poll for display changes, and there really aren't reliable events in place to recalc. Been thinking a lot about how to solve this one...

@Jastos

This comment has been minimized.

Jastos commented Jan 29, 2015

The CSS solution by Jens worked great for me. Slick inside of Foundation Tabs was giving me a hard time. Thanks!

@beingtree

This comment has been minimized.

beingtree commented Feb 21, 2015

thanks, @alexrussell: your solution worked for me using slick inside a Foundation 5 accordion.

@leecollings

This comment has been minimized.

leecollings commented Feb 27, 2015

@alexrussell , sorry to ask, but would you be able to shed some light on how your code would be used with responsiveTabs? I've looked at their docs and some 'Events', but it doesn't seem to match up with Bootstraps. So far I've got:

$('[href="#before-after"]').on('shown.bs.tab', function (e) {
    $('.slick-carousel-responsive').resize();
});

The only thing I haven't changed so far is the shown.bs.tab. The docs for ResponsiveTabs is here: https://github.com/jellekralt/Responsive-Tabs#events

But I'm not sure if I simply have to replace shown.bs.tab with tabs-activate or another event shown.

Essentially, I believe I'm having hte same issues as everyone else, when the page loads with another tab being shown, the slick width is displaying as 0, even when the tab is selected.

Can you or anyone else help?

@alexrussell

This comment has been minimized.

alexrussell commented Mar 5, 2015

@leecollings sorry for the delay getting back to you, I kept forgetting to come and read this properly after seeing it in my emails. I haven't tested this but from the demo given by the repo itself something like this will work:

$('#tabContainer').responsiveTabs({
    // ...
    activate: function(e, tab) {
        if (tab.selector == '#carousel') {
            $('.slick-carousel-responsive').resize();
        }
    },
    // ...
});

The if (tab.{something} == {something}) part can be any kind of check you want. In the example they give, if simply looks at tab.id, which gives you the 0-based tab index. In my book that's not safe to rely on, hence using tab.selector looking for a given id above. Looks like the tab object itself has a few things you can use to check whether or not your tab is the one selected (tab.id, tab.selector, tab.panel, tab.anchor to name the four most handy).

Also, instead of passing this function to the bootstrapping of responsiveTabs you could also use jQuery event binding as you suggest above:

$('#tabContainer').responsiveTabs({
    // ...
});

// ...

$('#tabContainer').on('tabs-activate', function(e, tab) {
    if (tab.selector == '#carousel') {
        $('#slick-carousel-responsive').resize();
    }
});
@LDigital84

This comment has been minimized.

LDigital84 commented Jun 28, 2015

I ran into this issue recently and saw an answer on StackOverflow that worked great for me. The code:

$('.slick-slider').get(0).slick.setPosition();

IE:

$( ".news-link" ).on( "click", function() {
  $( ".news-container" ).slideToggle( "slow" );
  $('.news-items').get(0).slick.setPosition() //.news-items is class for the slider.
  return false;
});
@mufaddalmw

This comment has been minimized.

mufaddalmw commented Jul 28, 2015

@patrickvaillancourt your solution is tricky and worked for me, Thanks :)

@yangkennyk

This comment has been minimized.

yangkennyk commented Aug 22, 2015

@Lynda333

worked wonderfully for me. thanks mucho!

@Marionovello

This comment has been minimized.

Marionovello commented Sep 17, 2015

This worked for me (directly from Slick site)
// Manually refresh positioning of slick
$('.your-element').slick('setPosition');

@jamesonnuss

This comment has been minimized.

jamesonnuss commented Sep 24, 2015

I am using JQuery UI Tabs and was struggling with this issue all day. Thank you!! @JensNielsen81 The CSS works perfectly.

@moog16

This comment has been minimized.

moog16 commented Oct 2, 2015

@Marionovello that worked perfectly for me! .resize() was working for me on Chrome/Firefox, but not on IE9. setPosition did the trick.

@camiloperezv

This comment has been minimized.

camiloperezv commented Nov 18, 2015

i solved in this way (coffeescript)

angular.element('#new-report').show( "slow",()->
angular.element('#slider-report').get(0).slick.setPosition();
);

@d-baranowski

This comment has been minimized.

d-baranowski commented Jun 15, 2016

I did something like this. It works but it's a bit twitchy. Let me know if you figure out a smoother version. NOTE: collapsible is a class i have appended myself to my collapsible elements, for example:

<div class="collapsible in">
            <textarea type="text" name="comment" style="resize: none;"></textarea>
</div>

Where class 'collapsible' is my own class to be used in JS and 'in' is a bootstrap class.

function resizeOnPanelCollapse() {
    $('.collapsible').on('shown.bs.collapse', function (e) {
        $('.slick-slider').each(function() {
          $(this).slick("getSlick").refresh();
        });
    });

    $('.collapsible').on('hidden.bs.collapse', function (e) {
        $('.slick-slider').each(function() {
          $(this).slick("getSlick").refresh();
        });
    });
}

Version 2 of my solution

function setSlideHeightAuto() {
    $('.slick-list').each(function() {
      $(this).attr("style", "height: auto;");
    });
}

function refreshHeight() {
    $('.slick-slider').each(function() {
      $(this).slick("getSlick").refresh();
    });
}

function resizeOnPanelCollapse() {
    $('.collapsible').on('show.bs.collapse', function(e) {setSlideHeightAuto()});
    $('.collapsible').on('hide.bs.collapse', function (e) {setSlideHeightAuto()});
    $('.collapsible').on('shown.bs.collapse', function (e) {refreshHeight()});
    $('.collapsible').on('hidden.bs.collapse', function (e) {refreshHeight()});
}
@proweb

This comment has been minimized.

proweb commented Jun 22, 2016

@patrickvaillancourt thanks man, you saved me hours. It's fixed my landing.

@TimmyDodd

This comment has been minimized.

TimmyDodd commented Jun 29, 2016

thanks @patrickvaillancourt !
I used this code to make it work in a modal:

.modal {
    display: block;
    visibility: hidden;
    overflow-y: hidden;
}
.modal.in {
    visibility: visible;
}
@clauz

This comment has been minimized.

clauz commented Aug 10, 2016

After playing with js to set min widths for the content and getting very very close but always having an issue with the transitions, I came across this and the resize() did it for me....thank you!!!!!!!!!!!!!

@Myrkotyn

This comment has been minimized.

Myrkotyn commented Jan 10, 2017

@patrickvaillancourt thank you, man

@JeremyLopez

This comment has been minimized.

JeremyLopez commented Jan 26, 2017

I didn't use bootstrap and wrote my own equivalent to bootstrap tabs, but when I try to fire $(this).slick("getSlick").refresh() when they click the tab, it doesn't work. Is there anything else I can do?

@mariovalney

This comment has been minimized.

mariovalney commented Feb 12, 2017

@JeremyLopez try @alexrussell suggestion:

$('.your-tab-trigger').on('click', function (e) {
    $('.your-slick').resize();
});
@samjonesigd

This comment has been minimized.

samjonesigd commented Feb 21, 2017

$('.your-slick').slick("getSlick").refresh();

this works great on click and a few other suggestions worked too on opening and closing tabs, but if i resize the page then open a tab my slick is broke until it goes to the next slide. This could be an issue when people are changing orientation on devices. Any suggestions?

@hot-Whiskey

This comment has been minimized.

hot-Whiskey commented May 2, 2017

@TimmyDodd worked for me! thank you! also, changing event handler from 'show.bs.modal' to 'shown.bs.modal' works (comes with lag, though)

@gety9

This comment has been minimized.

gety9 commented May 10, 2017

patrickvaillancourt, thanks!

Your solution worked for me!

@tijanmdr

This comment has been minimized.

tijanmdr commented Jul 3, 2017

Thanks to @painreign.. The solution works

$('.slick-div-holder').each(function() {
     $(this).slick("getSlick").refresh();
});

But this flickers alot

@AbdallahNasser

This comment has been minimized.

AbdallahNasser commented Jul 29, 2017

unfortunately, I tried all the solutions above, but none of them worked correctly with me
In my case, I have 2 tab containers in the same page, so when I try the bootstrap hack, it works for the slick, but it results an overflow within the other tab container (tab panes are overflowing each other)
is there any thing else to do ?

@leggomuhgreggo

This comment has been minimized.

Collaborator

leggomuhgreggo commented Jul 29, 2017

@AbdallahNasser So the reason it breaks is because by default the content in a non-active tab is hidden with display: none, which prevents the slick JS from getting the dimensions properly.

So you can use visibility hidden, which hides things without taking them out of the document flow, and position absolute all except one of the .tab-panes so only one of them is setting height in the document flow.

.tab-content>.tab-pane {display: block;visibility: hidden;}
.tab-content>.tab-pane.active {visibility: visible;}
.tab-content{position: relative;}
.tab-pane:not(:first-child){position: absolute;width: 100%;height: 100%;top: 0;left: 0;}

Example

Note: This assumes all the tab panes have the same height, which might not be the case. If each tab pane needs to be able to set the height of the parent through the document flow, then you should probably use a horizontal offset to hide/show.

@tibelchior

This comment has been minimized.

tibelchior commented Sep 19, 2017

None of the above worked in my case, I had to set a timeout to execute the refresh.

$('.depos').on('click', function () {
	setTimeout(function() {
	   $('.slider-nav').slick("getSlick").refresh();
	   $('.slider-for').slick("getSlick").refresh();
	},300);
});
@tijanmdr

This comment has been minimized.

tijanmdr commented Sep 19, 2017

@tibelchior Yeah, to work with slick with AJAX requests, u always have to set timeouts or refresh or deactivate and activate slick.

Better to use other sliders rather than slick.

@tibelchior

This comment has been minimized.

tibelchior commented Sep 19, 2017

@tijanmdr which one do you recommend that is as good as Slick?
I'm having special problem to execute Animate.css. It just works in the 1st slide

@tijanmdr

This comment has been minimized.

tijanmdr commented Sep 21, 2017

I think Owl Carousel is also a good one. Try that one out.

@kabelo38

This comment has been minimized.

kabelo38 commented Sep 26, 2017

this worked for me .nav-item its a class found on tabs menu

       $('.nav-item').click(function() {

            $('.slick-disabled').trigger('click');
        })
@mladimatija

This comment has been minimized.

mladimatija commented Nov 5, 2017

If anyone is still having this issue - seems like if you change the .slick-active slide in any way via JS after initialization of slick slider it will work as expected.

So, after you initialize your slider just add for example yourSlider.find('.slick-active').css('z-index', 1); and it will will work :)

@kstratis

This comment has been minimized.

kstratis commented Nov 24, 2017

@patrickvaillancourt @JensNielsen81 Thanks for the elegant solution!
I spent half a day of frustration and experimentation with tens of slick suggestions to no avail.
Your pure css solution was the only one that truly worked for me the way I wanted it to.

@TwelveLabours

This comment has been minimized.

TwelveLabours commented Dec 24, 2017

SIMPLE SOLUTION Thanks @LDigital84

I have a site where 'slick' is within a hidden div, and then shown during a transition. Slick's parent div is set to 'hidden' with initial CSS, and shown later with JQuery. Slick wouldn't appear, even though during initial formatting and testing (with the parent div NOT hidden) Slick had been performing as expected.

I simply use a hacky function call:

function slickFix(){
$('.yourSlickClass').get(0).slick.setPosition();
}

and then call that function after I show the parent div.

Works a charm.....thanks, this was driving me crazy!

@magicjoef

This comment has been minimized.

magicjoef commented Jan 5, 2018

I wasn't getting very far with the setPosition or triggering resize approaches, maybe because I had a slightly different context. Just in case this helps anyone...

I needed to switch between a few different slick sliders, meaning I wanted a couple of hidden sliders on load that I swapped in on a button click. However, using display none leads to the cited problem of Slick not being able to get dimensions.

I thought about flashing them in on load and then hiding them, as they are near the bottom of my page, that way Slick could init them. However, that's not tidy and still left the issue of what happened if someone resized the browser. All the hidden sliders wouldn't be resized correctly.

In the end I decided to create Slick everytime I needed it. Using a combination of a jQuery fadeIn followed by initialising slick worked well. I thought it might flash content, but it hasn't in the browsers I've tested.

When I swap to a new slick carousel, I hide the existing one and unslick it. Then for the incoming carousel I use the combination of fadeIn and initialising. Kind of a just in time approach. Something to consider depending where and how you're using Slick.

It seems stable on what I've tested, but if anyone can see any pitfalls to this approach I haven't spotted please let me know.

@codacopia

This comment has been minimized.

codacopia commented Feb 6, 2018

@painreign solution was the only thing that would work for me with multiple slide modals on a page.

@bomzj

This comment has been minimized.

bomzj commented Aug 2, 2018

I implemented universal code for all type of containers: tabs, modals, collapsible's and etc that finds hidden parent container where slick is in and updates slick position each time parent container is displayed (display: block).

function isHidden(el) {
	var style = window.getComputedStyle(el);
	return style.display === 'none';
}

function findHiddenParent(el) {
	while (el) {
		el = el.parentElement;
		if (el && isHidden(el)) return el;
	}
}

function onParentChanged(slickSlider) {
	if (this.style.display != 'none') {
		$(slickSlider).slick('setPosition');
	}
}


var slickSlider = $(".slick").slick({...});

// Slick can not calculate its dimension while being placed in hidden modals, tabs, collapsibles and etc(display: none)
// When hidden parent gets visible, we should do slick resize() to display it appropriately
var hiddenParent = findHiddenParent(slickSlider);
if (hiddenParent) {
	var observer = new MutationObserver(onParentChanged.bind(hiddenParent, slickSlider));
	observer.observe(hiddenParent, { attributes: true });
}
@saxsax1995

This comment has been minimized.

saxsax1995 commented Aug 29, 2018

Okay so i was stuck in this sitution just like you guys and i found the reason it cause the issue, it simple because when you use slick in tabs, it gonna set your unused tab to display: none, and when they did, the width in that unused tab become 0, and slick need a width to run correctly, and this solution worked for me:

.tab-content>.tab-pane{
  display: flex !important;
  height: 0px;
  overflow: hidden;
}

.widget-tab.mixed-tab .tab-pane.active{
  height: auto;
}

Set non-active tab to display: flex !important; and overflow: hidden; will do the same thing as display: none, but for some reason, it will set your width automatic, so the slick will works perfectly.

@bassm

This comment has been minimized.

bassm commented Nov 29, 2018

.tab-content>.tab-pane{ display: block; height: 0px; overflow: hidden; }
.tab-content>.active{ height: auto;}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment