metafizzy / flickity

:leaves: Touch, responsive, flickable carousels
https://flickity.metafizzy.co
7.52k stars 605 forks source link

Change settings at breakpoint #233

Open coreyworrell opened 9 years ago

coreyworrell commented 9 years ago

It would be nice to do something similar to slick where you can define settings that only kick in at certain breakpoints.

For example, on a wide screen I would like to show multiple items and allow free scrolling, but on a smaller mobile device I would like to only show one item at a time and snap to each item. I see that the showing one at a time option is doable now by setting the width to 100%, but changing freeScroll is not possible currently (as far as I can tell).

Is there a simple way to handle this?

Thank you.

desandro commented 9 years ago

Thanks for this feature request. +1 this issue if you'd like to see this feature added.

I've tried to make Flickity as flexible as possible so that layouts are handled entirely with your CSS. There's also the watchCSS option that allows you to enable or disable Flickity with breakpoints.

I'm not keen on adding this feature as its added complexity with small benefit. It requires mixing CSS logic with JS logic, which can be problematic. Many options, like freeScroll, assume you're only setting them at initialization. Changing options on the fly would require a large overhaul. That said, I'm always interested to see what developers are really wishing for.

richgcook commented 9 years ago

http://flickity.metafizzy.co/options.html#watchcss is just what I was after... being able to destroy/init the slider with your CSS/media queries is a game changer. Nice work, @desandro.

piotrkulpinski commented 8 years ago

+1

amdad commented 8 years ago

+1

nonlinearcom commented 8 years ago

+1

michaelwoodruff commented 8 years ago

+1

The Owl Carousel has these options: http://www.owlcarousel.owlgraphic.com/demos/responsive.html

warrebuysse commented 8 years ago

+1 indeed, I hoped to see the same like what Owl Carousel has. Before I've been using Owl Carousel in pretty much every project. Our team changed to Flickity because it was very promising. This feature however is key.

An alternative solution is to make two options. 1 would be mouseDraggable and the other would be touchDraggable. So we still have the touchdrag on mobile & tablets.

addedlovely commented 8 years ago

+1

linnett commented 8 years ago

+1

ghost commented 8 years ago

+1

desandro commented 8 years ago

You can set options according to media queries with matchMedia. This will set the option on initialization, but not on resize. Some options are hard to re-set after initialization, like freeScroll, draggable, and cellAlign. But I think this solution will suit most of your requests. See demo http://codepen.io/desandro/pen/xVBpqG

// Flickity options, defaults
var options = {
  prevNextButtons: false
};

// enable prev/next buttons at 768px
if ( matchMedia('screen and (min-width: 768px)').matches ) {
  options.prevNextButtons = true;
}

// disable draggable at 1200px
if ( matchMedia('screen and (min-width: 1200px)').matches ) {
  options.draggable = false;
}

$('.gallery').flickity( options );
desandro commented 8 years ago

This demo is now linked in Extra demos

portedison commented 7 years ago

Using vanilla js, I found this worked.

// force options to transition instantly
featureFlkty.options.selectedAttraction = 1;
featureFlkty.options.friction = 1;
// select item
featureFlkty.select(i);
// reset defaults
featureFlkty.options.selectedAttraction = 0.025;
featureFlkty.options.friction = 0.28;
desandro commented 7 years ago

@PortEdison You can select instantly with the third parameter in select

flkty.select( index, isWrapped, isInstant )
//
flkty.select( i, true, true )
nChauhan91 commented 7 years ago

Can these options be somehow used on resize, I'm trying to implement few options without re-creating the slider on resize, i'm able to pass correct value to the plugin but it doesn't work while resizing.

Thanks

EarthlingDavey commented 6 years ago

Heres a quick snippet of how I change the flickity options on resize. It's specific for my project but should be easy to adapt, sorry I don't have time to make a more generic example at the moment...

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
    var timeout;
    return function () {
        var context = this, args = arguments;
        var later = function () {
            timeout = null;
            if (!immediate) {
                func.apply(context, args);
            }
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) {
            func.apply(context, args);
        }
    };
}

var latestMachines = debounce(function () {

    var $latestMachines = $('.latest-machines .product-cards>.row');

    // set flickity options - small first
    var latestMachinesOptions = {
        // options
        cellAlign: 'left',
        contain: true,
        prevNextButtons: false,
        pageDots: false,
        wrapAround: true,
        groupCells: 1
    };

    // group cells and align center for large
    if (matchMedia('screen and (min-width: 1000px)').matches) {
        latestMachinesOptions.groupCells = 2;
        latestMachinesOptions.cellAlign = 'center';
    }

    if ($latestMachines.hasClass('flickity-enabled')) {

        // get flickity data
        var flkty = $latestMachines.data('flickity');

        if (latestMachinesOptions.groupCells == flkty.options.groupCells || latestMachinesOptions.cellAlign == flkty.options.cellAlign) {
            // do nothing, flickity options are correct
            return false;
        } else {
            // flickity options are incorrect
            // destroy slider
            $latestMachines.flickity('destroy');
        }
    }
    // init (or re-init) flickity
    $latestMachines.flickity(latestMachinesOptions);

}, 250);

if ($('.latest-machines .product-cards>.row').length > 0) {

    latestMachines();
    window.addEventListener('resize', latestMachines);

}
jerome-toole commented 6 years ago

The new draggable: '>1' option takes care of this if all you want to do is disable dragging. If you want to disable flickity based on slide count, then I found this solution to work well (using watchCSS):

https://github.com/metafizzy/flickity/issues/278#issuecomment-266070668

saltcod commented 6 years ago

Hey all. I didn't see this example out there, so I wanted to leave it here.

I want to instantiate flickity for small screens, then destroy it when we get up to large screens. I'm using enquirejs for the media query testing, but matchMedia would work too. This might not be the right/best way to do this, but I think it does work.

import enquire from 'enquire-js';
import Flickity from 'flickity';

/**
 * Initialize flickity slider on mobile, destroy it when we go
 * back to desktop width
 */

const columnSlider = document.querySelector('.columns');

export default enquire.register('screen and ( max-width: 767px )', {
    match: () => {
        const flkty = new Flickity(columnSlider, {
            cellAlign: 'left',
            contain: true,
            pageDots: false
        });
    },
    unmatch: () => {
        const unflkty = new Flickity(columnSlider).destroy();
    }
});
caseyjhol commented 6 years ago

@desandro Why is this issue closed? As far as I can tell, it's still not possible to change options after initialization.

tpinne commented 6 years ago

The linked demo in this comment only shows how to set options based on breakpoint on page load but not on page resize.

mikespineu commented 5 years ago

Why this issue is closed? Demos provided by desandro doesn't resolve problems with different settings for breakpoints for example "groupCells".

saltcod commented 5 years ago

Not directly solved with the plugin, but my solution works perfectly using enquire or just plain match media:

https://github.com/metafizzy/flickity/issues/233#issuecomment-388374039

Julix91 commented 5 years ago

Another attempt at this - handles multiple carousels in case you have multiple in one page that share the same break point. Should probably add logic to handle being given a non-array (i.e. just one carousel) as well ...

function toggleDraggableAtFlickityBreakpoint(mediaQuery, $carousels) {

    if(!mediaQuery || !$carousels) { return; }

    var dragToggles = function (match) {
        $carousels.forEach(function($carousel) {
            var flkty = $carousel.data('flickity');
            if(!flkty) { return; }

            if (match) {
                flkty.options.draggable = true;
            } else {
                flkty.options.draggable = false;
            }
            flkty.updateDraggable();
        });
    };

    var mql = window.matchMedia(mediaQuery);
    dragToggles(mql.matches);

    // listener
    mql.addListener(function(e){
        dragToggles(e.matches);
    });
}
// examples
toggleDraggableAtFlickityBreakpoint("(max-width: 768px)", [$carouselMain, $thatSecondSlider]);
toggleDraggableAtFlickityBreakpoint("(max-width: 1024px)", [$thatMoreDraggableSlider]);
codefinite commented 5 years ago

I've recently migrated from Slick and have been sorely missing this feature, I've come up with an idea to leverage the existing watchCSS so it can do more than just enable/disable Flickity, by adding the ability to trigger setting changes. Here's an example of how it could work:

JS

var flkty = new Flickity( elem, {
  watchCSS: true
  groupCells: 2,
  responsive: {
    small: {
      groupCells: 1
    }
  }
});

CSS

/* enable Flickity default settings */
.carousel:after {
  content: 'flickity';
  display: none; /* hide :after */
}

@media screen and ( max-width: 480px ) {
  /* enable Flickity small settings */
  .carousel:after {
    content: 'flickity.small';
  }
}

You have a new setting, an object for storing your breakpoints, (in the example I've named it "responsive"), the property names become the identifiers for your breakpoint and you trigger them by appending the breakpoint identifier to the .carousel:after content. The breakpoints contain an object where you can override any of the existing Flickity settings.

This keeps the settings and styles separate, yet allows for triggering settings changes with any CSS media query.

Edit:

I've put together a proof of concept that seems to be working, I found that Flickity only checks that watchCSS is truthy, so no need to add a new option, we can put the responsive object as the value of watchCSS and watchCSS will still evaluate as true.

var flkty = new Flickity( elem, {
  watchCSS: {
    small: {
      groupCells: 1
    }
  },
  groupCells: 2
});

CSS as above

if (typeof Flickity === 'function') {

  var proto = Flickity.prototype;

  proto.watchCSS = function () {
    var watchOption = this.options.watchCSS;
    if (!watchOption) {
      return;
    }

    var afterContent = getComputedStyle(this.element, ':after').content;
    // activate if :after { content: 'flickity' }
    if (afterContent.indexOf('flickity') != -1) {
      // ---modification start---
      // check for watchCSS options changes
      if (typeof watchOption === 'object') {
        this.watchCSSOptionsChange(afterContent);
      }
      // ---modification end---
      this.activate();
    } else {
      this.deactivate();
    }
  };

  proto.watchCSSOptionsChange = function (afterContent) {
    // store the current breakpoint identifier
    if (!this._currentOptionsIdentifier) {
      this._currentOptionsIdentifier = '';
    }
    // trim flickity. and surrounding quotes from the new breakpoint identifier
    var identifier = afterContent.substring(0, afterContent.length - 1).substring(10);

    // check for breakpoint change
    if (this._currentOptionsIdentifier !== identifier) {

      console.log(identifier);

      // if the original options have been cloned apply them to reset
      if (typeof this.options._flickityInitialOptions === 'object') {

        this.options = this.options._flickityInitialOptions;
        this._currentOptionsIdentifier = '';

        console.log('reset');
      }

      // check if the new breakpoint options exist
      if (typeof this.options.watchCSS[identifier] === 'object') {

        // clone the original options so we can reset on breakpoint change
        this.options._flickityInitialOptions = JSON.parse(JSON.stringify(this.options));

        // apply the new options
        var newOptions = this.options.watchCSS[identifier];

        for (var key in newOptions) {
          if (newOptions.hasOwnProperty(key)) {
            this.options[key] = newOptions[key];
          }
        }

        // update the identifer so we can skip if there's no change in breakpoint
        this._currentOptionsIdentifier = identifier;

        console.log('responsive');
      }

      console.log(this.options);
    }
  }
}

I'm modifying the watchCSS method to check if options.watchCSS is an object, if not the only additional code running is a typeof check.

The new watchCSSOptionsChange function could potentially be stand alone and triggered using the ready and resize events to avoid modifying the watchCSS method.

mrspence commented 4 years ago

Any update on this?

bobbaker404 commented 4 years ago

For my purposes, I wanted to change the groupCells based on breakpoint. I'm sure this method can be used with changing other Flickity options as well.

My plan was to initi Flickity based on the current breakpoint (xs, sm, md, lg, xl, xxl). 1 groupCells for xs, sm & md; 2 groupCells for anything bigger. On resize, check the breakpoint & reset the number of groupCells. If the number of groupCells changed, destroy Flickity & re-init with the new groupCells count.

I found this lovely article about setting breakpoints in CSS & using them within Javascript, so I gave it a try and I'm quite happy with the results.

Here's my code:

    // Flickity set-up
    var flkty;
    var elem = document.querySelector('.testimonial-carousel');
    var groupCells;

    // Set-up the breakpoint variable
    var breakpoint;

    // Get the current breakpoint
    var getBreakpoint = function () {
      return window.getComputedStyle(document.body, ':before').content.replace(/"/g, '');
    };

    // Set the number of groupCells based on breakpoint
    var setCells = function () {
      if (breakpoint === 'xs' || breakpoint === 'sm' || breakpoint === 'md') {
        return 1;
      } else {
        return 2;
      }
    };

    // Calculate breakpoint on page load
    breakpoint = getBreakpoint();
    groupCells = setCells();
    // console.log(groupCells);
    do_flickity();

    // Setup a timer
    var timeout;

    // Recalculate breakpoint on resize
    window.addEventListener('resize', function () {
      if (timeout) {
        window.cancelAnimationFrame(timeout);
      }
      timeout = window.requestAnimationFrame(function () {

        // Get the groupCells value from before resize
        var prevGroupCells = groupCells;

        // Get the new current breakpoint
        breakpoint = getBreakpoint();

        // Get the new groupCells value
        groupCells = setCells();

        // If new groupCells count is different from previous. DESTROY Flickity & init new Flickity
        // console.log(groupCells + ' | ' + prevGroupCells);
        if( groupCells !== prevGroupCells ){
          flkty.destroy();
          do_flickity();
        }

      });
    }, false);

    // init Flickity
    function do_flickity() {
      flkty = new Flickity( elem, {
        groupCells: groupCells,
        prevNextButtons: false,
      });
    }
Hlsgs commented 4 years ago

@desandro This is my and many others' deal braker when it comes to migrating to Flickity.

zzseba78 commented 4 years ago

Any news on this?

kazunorimiura commented 4 years ago

+1

obliviga commented 3 years ago

+1

umkasanki commented 3 years ago

+8908765 !!!

nikkster311 commented 3 years ago

What about this? https://flickity.metafizzy.co/options.html image

TomSinclair commented 3 years ago

Any update on bringing this functionality to Flickity?

phucbm commented 3 years ago

Hi guys,

I've been using Flickity for a while and have had an urge for this responsive option, so I created a jQuery plugin to handle this and am quite happy with the result, here is how I control responsive with the extra plugin

// Init Flickity
$carousel.flickityResponsive({
    cellAlign: "left",
    contain: true,
    freeScroll: true,
    responsive: [
        {
            breakpoint: 1024,
            settings: {
                wrapAround: true,
                cellAlign: "center",
                freeScroll: false,
                prevNextButtons: false,
                pageDots: false
            }
        }
    ]
});

The plugin will decide when to destroy and re-initialize the carousel to apply new options using matchMedia(). You might notice that the usage was inspired by Slick. I published the code at my repository for maintenance purposes 👉 https://github.com/phucbm/flickity-responsive There's also a CodePen for it 👉 https://codepen.io/phucbui/pen/ExmJVZa

davemac commented 3 years ago

Thankyou @phucbm this is just what I was looking for. It seems to be a very common requirement that groupCells should change on different screen sizes - would be great to see this added to Flickity.

obliviga commented 3 years ago

+1

kevin-vo commented 3 years ago

+1

fozdemir40 commented 2 years ago

Works amazing @phucbm thank you!

dijkermans commented 2 years ago

+1

hiphopappotamus commented 2 years ago

+1

joebentaylor1995 commented 1 year ago

Such a shocking and disappointing decision to make this an extra - rather than integrated into the slider... glad i went to SwiperJS

scheylon commented 1 year ago

Still no breakpoint feature ?