kenwheeler/slick

Variable height on vertical carousel?

Opened this issue · 31 comments

Hey,

I've been using Slick for a year or so on various projects, and it is truly amazing! However I've hit a bit of a problem on a vertical carousel.

It's a leaderboard showing scores - I need to show six at a time out of about 30, hence vertical is the way I need to go, although it's the first time I've used this setting.

The problem I've got is responsive - on large desktop each item starts out as 60px height each, dictated by a square image, but with text on one line, so the overall height of the container is 360px (6x60). The elements don't have a fixed height, it's the image the makes it 60px.

But when I scale down to mobile, and the text starts to wrap onto two lines making each item taller than 60px, the carousel doesn't resize - it stays at 360px, with that height measurement being applied by the plugin.

It's as if there needs to be a 'variableHeight' setting, similar to the variableWidth setting for horizontal carousels.

Is there a way around this currently?

Can you please create a jsfiddle showing the behavior?

I'm highly interested myself, since we need this functionality for a client. Will provide a fiddle next week.

Yeah, seems to be same issue as at least #1852. I've already provided a fiddle here: https://jsfiddle.net/yypesh3d/7/

wtran commented

vote for this feature

Currently having this workaround:

// Calculate the heighest slide and set a top/bottom margin for other children.
// As variableHeight is not supported yet: https://github.com/kenwheeler/slick/issues/1803
var maxHeight = -1;
$('.slick-slide').each(function() {
  if ($(this).height() > maxHeight) {
    maxHeight = $(this).height();
  }
});
$('.slick-slide').each(function() {
  if ($(this).height() < maxHeight) {
    $(this).css('margin', Math.ceil((maxHeight-$(this).height())/2) + 'px 0');
  }
});

@iampuma That workaround fixed my problem perfectly. Thanks! Hopefully something along those lines gets worked into the official branch at some point.

Figured out how to let slick slider calculate the proper offset: currently slick uses the outer height of the first slider item and multiplies it with the slide index on each slide (getLeft function).

We've extended the getLeft function letting it count the sum of the outer heights of all previous slides:

if (_.options.vertical === false) {
    targetLeft = ((slideIndex * _.slideWidth) * -1) + _.slideOffset;
} else {
     //targetLeft = ((slideIndex * verticalHeight) * -1) + verticalOffset;
     var curIndex = _.currentSlide;
     var sum = 0;
     for (var i = 0; i < curIndex; i++) {
         sum = sum + _.$slides.eq(i).outerHeight(true);
    }
    targetLeft = (sum * -1) + verticalOffset;
}
fleps commented

@IMPLATRIX I tested your code on a simple vertical slider where some slides have different height and didn't worked, while also breaking the slide change animation (i'm using centerMode and focusOnSelect)

@fleps just tested it and it's also working with centerMode and focusOnSelect. You can see our workaround in action here: http://dev.riwa-architekten.de/innenarchitektur/einfamilienhaus-in-duesseldorf-urdenbach.html
Our animation also got broken and we fixed it with adding an extra CSS transition to our slider's .slick-track:

.vertical-image-slider .slick-track { transition: .5s linear; }

fleps commented

@IMPLATRIX ah now I see. My case is different than yours, I have a vertical slider that is almost 100% height but my slides are not using the entier height, they little boxes and I show 3 at time clipped in the center, it's a totally different scenario =)

@iampuma is it possible to make your workaround responsive? On smaller viewports the margin becomes too big for contents with smaller heights.

@lucasskywalker I try your code

        if (_.options.vertical === false) {
            targetLeft = ((slideIndex * _.slideWidth) * -1) + _.slideOffset;
        } else {
-            targetLeft = ((slideIndex * verticalHeight) * -1) + verticalOffset;
+            verticalOffset = 0;
+            for (var i = 0; i < slideIndex; i++) {
+                verticalOffset += _.$slides.eq(i).outerHeight(true);
+            }
+            console.log((verticalOffset * -1));
+            targetLeft = (verticalOffset * -1);
        }

but doesn´t work - I have different gaps at top of slider

zwischenablage04

The code from here #1803 (comment) slide the first item and after them "jump" the netxt item at the top position :(

At another page https://www.gut-cert.de/home.html I use the carouFredSel (https://github.com/Codeinwp/carouFredSel-jQuery) - works... but I´ll change all sliders to slick

with the slick slider the problem is known since 2015 - a fix would be great

Hi! Can you send me the code you use in your project? proweb.090@gmail.com

@lucasskywalker I sended a email to you

add new fixed for slick options => infinite = true, centerMode: false;

@lucasskywalker thanks for your code - are you doing PR, or should I do PR?

btw: is the project still supported by @kenwheeler ? over 700 issues are open and there are almost 150 PRs?

/*
        if (_.options.vertical === false) {
            targetLeft = ((slideIndex * _.slideWidth) * -1) + _.slideOffset;
        } else {
            targetLeft = ((slideIndex * verticalHeight) * -1) + verticalOffset;
        }
*/
// NEW
        if (_.options.vertical === false) {
              targetLeft = ((slideIndex * _.slideWidth) * -1) + _.slideOffset;
            } else {
              verticalOffset = 0;
        
              for (let i = 0; i < slideIndex; i++) {
                verticalOffset += _.$slides.eq(i).outerHeight(true);
              }
        
              if (_.options.infinite === true) {
                Array.from(_.$slideTrack[0].children).forEach((slide) => {
                  if (slide.dataset.slickIndex < 0) {
                    verticalOffset += $(slide).outerHeight(true);
                  }
                });
              }
        
              targetLeft = verticalOffset * -1;
            }
//

PR: #3565

@zonky2 using your solution, thanks!

One issue discovered - the animation is broken whenever I try going "prev" over a slide with more height than the rest

@zonky2 Your code throws syntax error in IE11.

@zonky2 Your code throws syntax error in IE11.

Because i use forEach ("forEach" doesn't work in IE11 => rewrite to "for")!!! And learn JS (ES6)

In case anyone looking for IE11 support for #1803 (comment). This is the updated code.

if (_.options.vertical === false) {
  targetLeft = ((slideIndex * _.slideWidth) * -1) + _.slideOffset;
} else {
  verticalOffset = 0;

  for (let i = 0; i < slideIndex; i++) {
    verticalOffset += _.$slides.eq(i).outerHeight(true);
  }

  if (_.options.infinite === true) {
    var $slideCounts = Array.prototype.slice.call( _.$slideTrack[0].children );
    for (var i = 0; i < $slideCounts.length; i++) {
      if ($slideCounts[i].dataset.slickIndex < 0) {
        verticalOffset += $($slideCounts[i]).outerHeight(true);
      }
    }
  }

  targetLeft = verticalOffset * -1;
}

I have a vertical slider which gets taller if slide(s) get taller from text wrapping.

I'd like to keep the overall height fixed, even if the slides inside change height randomly on resize. I'm using center mode and infinite and it just doesn't seem do all the dynamic math for heights -- i.e. keeping the center slide centered even if the slide below or above got taller from text wrapping.

My only work around is to use fixed heights for all slides and ellipsis the text so nothing ever wraps.

Also, I don't think @kenwheeler maintains this anymore, though I am grateful for this wonderful, free tool.

/ Calculate the heighest slide and set a top/bottom margin for other children.
// As variableHeight is not supported yet: https://github.com/kenwheeler/slick/issues/1803
var maxHeight = -1;
$('.slick-slide').each(function() {
  if ($(this).height() > maxHeight) {
    maxHeight = $(this).height();
  }
});
$('.slick-slide').each(function() {
  if ($(this).height() < maxHeight) {
    $(this).css('margin', Math.ceil((maxHeight-$(this).height())/2) + 'px 0');
  }
});

Thanks man! this work for vertical mode!

These approaches above didn't work for me to have variable height slides, but I was able to get it to determine height correctly in my scenario by changing the following sections of code:

if (_.options.vertical === false) {
  if (_.options.centerMode === true) {
      _.$list.css({
          padding: ('0px ' + _.options.centerPadding)
      });
  }
} else {
  // OLD
  // _.$list.height(_.$slides.first().outerHeight(true) * _.options.slidesToShow);
  // NEW
  let $verticalHeight = 0;
  _.$slides.each(function() {
    $verticalHeight = $verticalHeight + $(this).outerHeight(true);
  });
  _.$list.height(Math.ceil($verticalHeight));
  // END NEW
  if (_.options.centerMode === true) {
      _.$list.css({
          padding: (_.options.centerPadding + ' 0px')
      });
  }
}
if (_.options.vertical === false && _.options.variableWidth === false) {
  _.slideWidth = Math.ceil(_.listWidth / _.options.slidesToShow);
  _.$slideTrack.width(Math.ceil((_.slideWidth * _.$slideTrack.children('.slick-slide').length)));

} else if (_.options.variableWidth === true) {
  _.$slideTrack.width(5000 * _.slideCount);
} else {
  _.slideWidth = Math.ceil(_.listWidth);
  // OLD
  // _.$slideTrack.height(Math.ceil($verticalHeight));
  // NEW
  let $verticalHeight = 0;
  _.$slides.each(function() {
    $verticalHeight = $verticalHeight + $(this).outerHeight(true);
  });
  _.$slideTrack.height(Math.ceil($verticalHeight));
}

None of your solutions work fine, if you want to have true adaptive height with a vertical:true you have to add this code in "setDimensions" method (line 2042):
if(_.options.adaptiveHeight === true){ _.$list.height(_.$slides.eq(_.currentSlide).outerHeight(true)); } else { _.$list.height(_.$slides.first().outerHeight(true) * _.options.slidesToShow); }
also add adaptiveHeight:true with vertical:true.
you can add in CSS "transition: height 200ms ease; on slick-list for smoothing resize

@Chousse would you mind share your solution into JSFiddle or Codepen playground? :)

In case someone is using Vue!

First make the VueSlickCarousel component available in $ref by adding ref="carousel"

After that apply the following to the script of your component:

mounted() {
    window.addEventListener('resize', this.recalculateHeight);

    this.$nextTick(this.recalculateHeight);
},
destroyed() {
    window.removeEventListener('resize', this.recalculateHeight);
},
methods: {
    recalculateHeight() {
        const slides = this.$refs.carousel.$refs.innerSlider.$refs.track.$children;

        // Reset
        slides.forEach(slide => slide.$el.style.height = 'auto');

        // Calculate
        const highest = Math.max(...slides.map(component => component.$el.getBoundingClientRect().height))

        // Apply
        slides.forEach(slide => slide.$el.style.height = highest);
    },
}

Hi. I seem to have found a solution for slides of different heights. You need to add the following code to slick.js in getLeft function after line 1155:

if (_.options.vertical === true && _.options.variableHeight === true) {
    targetSlide = _.$slideTrack.children('.slick-slide').eq(slideIndex + _.options.slidesToShow);
    targetLeft = targetSlide[0] ? targetSlide[0].offsetTop * -1 : 0;
}

And add the parameter to the carousel settings:

variableHeight: true

Who can test and pull request, please do so.

Currently having this workaround:

// Calculate the heighest slide and set a top/bottom margin for other children.
// As variableHeight is not supported yet: https://github.com/kenwheeler/slick/issues/1803
var maxHeight = -1;
$('.slick-slide').each(function() {
  if ($(this).height() > maxHeight) {
    maxHeight = $(this).height();
  }
});
$('.slick-slide').each(function() {
  if ($(this).height() < maxHeight) {
    $(this).css('margin', Math.ceil((maxHeight-$(this).height())/2) + 'px 0');
  }
});

I had vertical slider.
For me in was need to add equal margin bottom for every slide item I need. So I added it by css.

.slider-item { margin-bottom: 40px; }

This I added to my custom script.
var maxHeight = -1; $('.your-slider .slick-slide').each(function() { if ($(this).height() > maxHeight) { maxHeight += $(this).height()+40; } }); $('.your-slider .slick-list').css('min-height', maxHeight-40 + 'px');

Sadly that we stiil don't have the options for this...