Skip to content
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

Disable prev/next buttons for contain #289

Open
dzucconi opened this issue Nov 17, 2015 · 18 comments
Open

Disable prev/next buttons for contain #289

dzucconi opened this issue Nov 17, 2015 · 18 comments

Comments

@dzucconi
Copy link

I'm wondering if you have any suggestions for determining if you've arrived at the boundary of the carousel when the contain option is specified. (So that one could say for instance disable the next or previous buttons).

When not using the indicator dots for position feedback this kind of behavior appears confusing, though I understand the logic here.

Example:

contain-carousel

@desandro
Copy link
Member

desandro commented Nov 17, 2015

Yes, this is a tricky issue. I'll repeat my thoughts on why page dots and the prev/next buttons work with contain #177 (comment) and #135 (comment)

When multiple cells are visible or with 'contain': true, there can be areas when clicking the previous/next arrows won't move the slider.

While the slider does not change, the selected cell does. This may be useful for accessibility, as it allows users to select a cell with buttons, rather than with mouse.

You can detect when the slider is at the end with contain with this code. See demo http://codepen.io/desandro/pen/KdEPJo?editors=001

flkty.on( 'cellSelect', function() {
  var target = flkty.selectedCell.target;
  if ( target == flkty.cells[0].target ) {
    console.log('is at contained start')
  } else if ( target == flkty.getLastCell().target ) {
    console.log('is at contained end')
  }
});

If you're looking to disable prev/next buttons, you could do it by hacking Flickity.PrevNextButton. See demo http://codepen.io/desandro/pen/LpaPya?editors=001

var PrevNextButton = Flickity.PrevNextButton;
PrevNextButton.prototype.update = function() {
  // index of first or last cell, if previous or next
  var cells = this.parent.cells;
  // enable is wrapAround and at least 2 cells
  if ( this.parent.options.wrapAround && cells.length > 1 ) {
    this.enable();
    return;
  }
  var lastIndex = cells.length ? cells.length - 1 : 0;
  var boundIndex = this.isPrevious ? 0 : lastIndex;
  var isEnabling;
  if ( this.parent.options.contain ) {
    var boundCell = cells[ boundIndex ];
    var selectedCell = cells[ this.parent.selectedIndex ];
    isEnabling = selectedCell.target != boundCell.target;
  } else {
    isEnabling = this.parent.selectedIndex == boundIndex
  }
  var method = isEnabling ? 'enable' : 'disable';
  this[ method ]();
};

I do not recommend disabling buttons like this as selecting cells is useful for accessibility.

@dzucconi
Copy link
Author

Ah, I see that makes sense. This issue is indeed trickier than I thought. The examples are a great help, thank you!

@desandro desandro reopened this Nov 18, 2015
@desandro desandro changed the title Event for when visual boundary is reached? Disable prev/next buttons for contain Nov 18, 2015
@desandro
Copy link
Member

Re-opening for other's visibility

@peduarte
Copy link

Having the same issues, thanks for the demos @desandro, really really helpful man.

However, I am still able to replicate the error by using a combination of dragging and clicking on prev/next. You can see it in both of the demos above. Start by dragging the slide to the very end (dragging left) until the last cell is highlighted in yellow.
Now, click on the previous button, you'll notice you need to click twice until the slider starts moving again. Not so much of an issue in this demo, as the highlighted cell changes, but in my current use case, it feels like a bug.

Wonder if would be worth having that as an option though, for example:
detectContainedEnd, setting to true would disable the next button when the last item is in view.

Thanks a lot

@dinealbrecht
Copy link

+1

I'm also still having issues with this when using a carousel as navigation for another carousel.

The fact is that in this case an item in the carousel can be selected by the user. Therefore that item then has a class 'is-selected' and if this selected item is the last item in the carousel, and the carousel is cell-aligned to the center or to the left, a click on the left button doesn't move the carousel anymore.

Even if the fix from above has been applied.

@musdy
Copy link

musdy commented Feb 2, 2016

Any update on this one @desandro?

@desandro
Copy link
Member

desandro commented Aug 5, 2016

One solution is to use groupCells, so that cells are grouped together. When you are in the last group, multiple cells are still visible, but the next button will be disabled.

@ghost
Copy link

ghost commented Aug 8, 2016

Also trying to solve this for a project with cell align left.

Got this maybe working;
Maybe it is a idea to add var boundLastSlide or something like that.
When a slide has the endBound on his target, then is know it is the last slide that is going to move.
slide.target = Math.min( slide.target, endBound );
And set that one as last slide.
[Example: When you have 6 slides, and number 4 is the last one that slides. Number 4 is the "fake last slide"]

Then u can bound the next button to a end.
example test code:

in the flickity create add this.boundLastSlide = null;

in function:
proto._containSlides = function() {
add after:
slide.target = Math.max( slide.target, beginBound ); slide.target = Math.min( slide.target, endBound );

This:
if ( slide.target == endBound) { var slideIndex = this.slides.indexOf(slide); if (this.boundLastSlide == null ) { this.boundLastSlide = slideIndex; } else if ( this.boundLastSlide > slideIndex ) { this.boundLastSlide = slideIndex; } }

Then this.boundLastSlide has the last slider.

On PrevNextButton.prototype.updatereplace:
var lastIndex = slides.length ? slides.length - 1 : 0; var boundIndex = this.isPrevious ? 0 : lastIndex; var method = this.parent.selectedIndex == boundIndex ? 'disable' : 'enable';
with:
if ( this.parent.options.contain || !this.parent.options.wrapAround || this.parent.cells.length || this.parent.boundLastSlide != null ) { var lastIndex = this.parent.boundLastSlide; if ( this.isPrevious ) { var method = this.parent.selectedIndex == 0 ? 'disable' : 'enable'; } else { var method = this.parent.selectedIndex >= lastIndex ? 'disable' : 'enable'; } } else { var lastIndex = slides.length ? slides.length - 1 : 0; var boundIndex = this.isPrevious ? 0 : lastIndex; var method = this.parent.selectedIndex == boundIndex ? 'disable' : 'enable'; }

// Not all edits given.

Only this doesn't solve the page dots, but can be a step to work with contain and the bounds.
Sorry if the post is not so clear. But hope the code tells something.

Maybe it can done better only in the post u made in nov 2015. #289 (comment)

Demo Video https://www.youtube.com/watch?v=5Oy_nYpB4No

@wydflannery
Copy link

wydflannery commented Sep 13, 2016

I'm having a similar issue with this. Grouping fixes the disable issue but I only want to scroll one <div> per click. I was thinking it would be helpful if there was an "in-viewport" class, similar to the "is-selected" that way you could disable the next button if the last-child was in the viewport.

ghost pushed a commit to Krielkip/flickity that referenced this issue Sep 14, 2016
Usage:
[object].flickity({
        cellAlign: 'left',
        contain: true,
        wrapAround: false
        });
@ghost
Copy link

ghost commented Sep 14, 2016

@wydflannery made this local in v1.2.1. ( because IE8 support needed 👎 ) and with settings:

cellAlign: 'left', contain: true, wrapAround: false, pageDots: false

Side note: I did not make a solution for the page dots. this would be simple with: boundLastSlide towards pageDots. In the function PageDots.prototype.setDots

Here you can find the code.
https://github.com/KrielkipNL/flickity/tree/containFix

Compare:
Krielkip/flickity@v1.2.1...KrielkipNL:containFix

Maybe this feedback helps @desandro
[Pin: http://codepen.io/anon/pen/VKaGBy]

For the new version (2.0.x) you need to check all possible options in contain.

@LDokos
Copy link

LDokos commented Sep 5, 2017

Hey guys!

If anyone still wondering about this, here is my solution:

var carouselInit = new Flickity( '.carousel-init', {
    cellSelector: '.carousel-cell',
    contain: true,
    cellAlign: 'left',
    pageDots: false,
    wrapAround: false,
    freeScroll: false
});

var lastCell = false;

carouselInit.on('cellSelect', function(){
    var friction = (carouselInit.options['friction'] + 0.1) * 1000;

    setTimeout(function(){
        var fullSize = carouselInit.slideableWidth,
            viewport = carouselInit.size['width'],
            cellWidth = carouselInit.selectedSlide['outerWidth'],
            movedX = Math.round(carouselInit.x);

        fullSize = Math.round(fullSize);
        cellWidth = Math.round(cellWidth);
        movedX = Math.abs(movedX);

        var total = Math.round((viewport + movedX) / 100) * 100,
            fullSize = Math.round(fullSize / 100) * 100;
            
        if(total >= fullSize){
            if(lastCell == true)
                carouselInit.select(0);

            lastCell = true;
        }

        if(movedX < 70)
            lastCell = false

    }, friction);
});

@Marvin1003
Copy link

Marvin1003 commented Nov 6, 2018

Another possible solution.

new Flickity('.slider', {
      contain: true,
      pageDots: false,
      cellAlign: "left",
      selected: 0,
      on: {
        ready: containFix,
        settle: containFix,
        change: containFixOnChange,
        resize: containFix
      }
    });

    function containFix() {
      const viewport = this.size.width;
      const movedX = Math.round(Math.abs(this.x));

      const total = viewport + movedX;
      const fullSize = Math.round(this.slideableWidth);

      toggle = toggle.bind(this);
      toggle(total, fullSize, viewport);
    }

    function containFixOnChange() {
      const viewport = this.size.width;
      const cellWidth = this.selectedSlide.outerWidth;

      if (this.selectedIndex > this.options.selected) var total = viewport + cellWidth * this.options.selected;
      else if (this.selectedIndex < this.options.selected) var total = viewport - cellWidth * this.options.selected;
      this.options.selected = this.selectedIndex;

      const fullSize = Math.round(this.slideableWidth - cellWidth);

      toggle = toggle.bind(this);
      toggle(total, fullSize, viewport);
    }

    function toggle(total, fullSize, viewport) {
      const cellWidth = this.selectedSlide.outerWidth;
      const maxIdx = this.cells.length - Math.round(viewport / cellWidth);

      if (fullSize > viewport) {
        this.bindDrag();
        this.element.classList.remove("slider--toggle-both");
      } else if (fullSize <= viewport) {
        this.unbindDrag();
        this.element.classList.add("slider--toggle-both");
      }

      if (total >= fullSize) this.element.classList.add("slider--toggle-next");
      else this.element.classList.remove("slider--toggle-next");

      if (this.selectedIndex > maxIdx) {
        this.select(maxIdx);
        this.element.classList.add("slider--toggle-next");
      }
    }

@vincentsmuda
Copy link

Another solution

// This works if the cells are the same width and aligned center. 
//
// If you have variants in your cell widths, you could loop through cells (beginning/end)
// to figure out which one will land on the middle of flkty.size.width
// 
// Also you can extract the floor/ceil computations to a resize event if it becomes intensive.
flkty.on( 'change', i => {

    // figure out the last/first slides
    let floor = ~~(flkty.size.width / flkty.cells[0].element.offsetWidth / 2),
        ceil = flkty.cells.length - floor;

    // select the floor/ceil cells if the current index exceeds them
    if(i > ceil) this.carousel.select(ceil);
    else if (i < floor) this.carousel.select(floor);

});

@nixondesigndev
Copy link

Would PR #1032 potentially fix this as we are redefining what counts as selected?

@cooltolia
Copy link

Hey. As for me, the easiest solution for this issue is to use the IntersectionObserver API.

Just listen to the last slide to get into the viewport, then disable Next Button

@iriepixel
Copy link

Hey. As for me, the easiest solution for this issue is to use the IntersectionObserver API.

Just listen to the last slide to get into the viewport, then disable Next Button

Hey, could you share an example please?

@cooltolia
Copy link

@iriepixel
Hey, sorry for so long response.
something like that

const observer = new IntersectionObserver(
    entries => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                controls.disableNext();
            } else {
                controls.enableNext();
            }
        });
    },
    { rootMargin: '0px -24px 0px 0px', root: sliderNode }
);
observer.observe(lastSlide);

where sliderNode is your parent node to init slider

@OverdoseDigital
Copy link

OverdoseDigital commented Mar 15, 2024

I used the on "scroll" function to check if the slider progress is 100%

scroll: function( progress ) {
              progress = Math.max( 0, Math.min( 1, progress ) );
              
              var next_buttons = document.querySelectorAll('.cross-sell-container-desktop .flickity-button.flickity-prev-next-button.next')
             
              if(progress == 1 && next_buttons ){
                next_buttons.forEach(function(button) {
                  button.disabled = true;
                });
              } else{
                next_buttons.forEach(function(button) {
                  button.disabled = false;
                });
              }
              
            }

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

No branches or pull requests