metafizzy / flickity

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

Enable scrolling via mousewheel #44

Open iamstiil opened 9 years ago

iamstiil commented 9 years ago

I'm experiencing a little problem with one of my sliders.

// Defined in seperate JS file
function scrollEnd(flickelem) {
    // FLICKITY DragEnd

    if ( flickelem.options.freeScroll ) {
        flickelem.isFreeScrolling = true;
    }
    // set selectedIndex based on where flick will end up
    var index = flickelem.dragEndRestingSelect();

    if ( flickelem.options.freeScroll && !flickelem.options.wrapAround ) {
    // if free-scroll & not wrap around
    // do not free-scroll if going outside of bounding cells
    // so bounding cells can attract slider, and keep it in bounds
    var restingX = flickelem.getRestingPosition();
    flickelem.isFreeScrolling = -restingX > flickelem.cells[0].target &&
    -restingX < flickelem.getLastCell().target;
    } else if ( !flickelem.options.freeScroll && index == flickelem.selectedIndex ) {
    // boost selection if selected index has not changed
    index += flickelem.dragEndBoostSelect();
    }
    // apply selection
    // TODO refactor this, selecting here feels weird
    flickelem.select( index );
}

// Inside a <script> tag inside the document
$(function(){
    $(document).ready(function(e) {
        var cellCount = $('.gallery-cell').length;
        var canWrapAround = false;
        if(cellCount > 1) canWrapAround = true;
        $('.text-slider').flickity({
            accessibility: true,
            autoPlay: false,
            cellAlign: 'center',
            cellSelector: undefined,
            contain: true,
            draggable: true,
            freeScroll: true,
            friction: 0.2,
            imagesLoaded: true,
            initialIndex: 0,
            percentPosition: true,
            prevNextButtons: false,
            pageDots: false,
            resize: true,
            rightToLeft: false,
            watchCSS: false,
            wrapAround: canWrapAround
        }).mousewheel(function(e) {
            e.preventDefault();
            var flickelem = Flickity.data(this);

            if (e.deltaX) {
                flickelem.x += e.deltaX * e.deltaFactor;
            }
            else if (e.deltaY) {
                flickelem.x += e.deltaY * e.deltaFactor;
            }
            scrollEnd(flickelem);
        });
        $(window).load(function(e) {
            $('.text-slider').flickity('resize');
        });
    });
});

I am using the jQuery mousewheelplugin to support scrolling inside flickity. But somehow on mobile devices occasionally I can't reach the last cell. It always flicks back to the previous one. Could it be a bug or could it be a fault of me?

desandro commented 9 years ago

Add a 👍 reaction to this issue if you would like to see this feature added. Do not add +1 comments — They will be deleted.


Thanks for taking a shot at this. I haven't looked in to supporting mousewheel, or providing an API to manipulate the slider position. I'll categorize this as a feature request for now. I'm focusing on releasing the core feature set for Flickity at the moment. I'll come back to this afterwards.

desandro commented 9 years ago

Reclassifying this issue as a feature request. Flickity does not currently support manipulation of the slider position. If you would like to see this feature, please +1 or subscribe to this issue.

nvartolomei commented 9 years ago

+1

gerwitz commented 9 years ago

+1

(I have a homegrown carousel in place at http://theartificial.nl/ and want to replace it, but mousey-scroll is important to me.)

FabianGabor commented 9 years ago

+1

iamstiil commented 9 years ago

Got something here ;)

$('.gallery').mousewheel(function(e) {
    e.preventDefault();
    var flkty = Flickity.data(this);

    if (!window.wheeling) {
        // console.log('start wheeling!');

        if(e.deltaX > 0 || e.deltaY < 0){
            flkty.next();
        } else if(e.deltaX < 0 || e.deltaY > 0){
            flkty.previous();
        }
    }

    clearTimeout(window.wheeling);
    window.wheeling = setTimeout(function() {
        // console.log('stop wheeling!');

        delete window.wheeling;

        // reset wheeldelta
        window.wheeldelta.x = 0;
        window.wheeldelta.y = 0;
    }, 250);

    window.wheeldelta.x += e.deltaFactor * e.deltaX;
    window.wheeldelta.y += e.deltaFactor * e.deltaY;
    if(window.wheeldelta.x > 500 || window.wheeldelta.y > 500 || window.wheeldelta.x < -500 || window.wheeldelta.y < -500){
        window.wheeldelta.x = 0;
        window.wheeldelta.y = 0;

        if(e.deltaX > 0 || e.deltaY < 0){
            flkty.next();
        } else if(e.deltaX < 0 || e.deltaY > 0){
            flkty.previous();
        }
    }

    // console.log(window.wheeldelta);

});

Found this approach over here: http://stackoverflow.com/questions/3515446/jquery-mousewheel-detecting-when-the-wheel-stops

damienroche commented 9 years ago

+1

gerwitz commented 9 years ago

I'm presently hacking my way through this, trying to engage Flickity's lovely touch/click behavior for horizontal scrolling. The best discussion about relevant issues seems to be this issue on the jQuery mousewheel plugin: https://github.com/jquery/jquery-mousewheel/issues/36

trstn-c commented 9 years ago

+1 This feature would make me a very happy human.

StefanEndress commented 9 years ago

+1

StefanEndress commented 9 years ago

Thanks @pavethiran, the approach you are recommending is really interesting, but it only scrolls to the next cell/element. Do you have an idea how to do an actual free mouse scroll with it. Would be so useful, because flickity would cover then almost every possible interaction with the user.

f0rmat1k commented 9 years ago

@gerwitz hello. You can try our wheel-indicator to implement wheel support for gallery.

tomagladiator commented 8 years ago

+1

brandexponents commented 8 years ago

+1

akisvolanis commented 8 years ago

+1

nevernotsean commented 8 years ago

+1

hybridvision commented 8 years ago

+1 :)

stevemckinney commented 8 years ago

+1

hybridvision commented 8 years ago

After much searching I found the functionality I was looking for in iDangerous' Swiper: https://github.com/nolimits4web/Swiper - it's also beautifully written and well documented so I'd recommend checking it out :)

Sjael commented 8 years ago

If this is still and issue then this might help someone: https://iamsteve.me/blog/entry/enhancing-horizontal-scrolling-with-flickity-js

desandro commented 8 years ago

I have decided to not add this feature. Mouse wheel are most commonly used for vertical scrolling. Flickity is horizontal. Adding this feature would add additional complexity for a small feature. Sorry to say no here!

LuudJanssen commented 6 years ago

For people who want to "hack in" mousewheel support, this was my approach:

wheel.addWheelListener(slider.$element[0], event => { const wheelNormalized = normalizeWheel(event); slider.applyForce(-wheelNormalized.pixelY / 4); slider.startAnimation(); slider.dragEnd(); });


This uses Flickity's internal force, animation and drag functions to perform slider movement and therefore correctly handles slide indexes, freescroll, friction etc.

**Note that this uses the `pixelY` property of the normalized wheel event. So it registers any vertical mouse scrolling as horizontal movement and does nothing with any horizontal mouse scrolling.**

**Also note that the `slider.dragEnd()` call fires the 'dragEnd' event with `undefined` parameters.**

This code example uses NPM packages and arrow functions. If you don't want to use NPM packages or arrow functions, use the following piece of code after loading [MDN's cross browser event listener for wheel events](https://developer.mozilla.org/en-US/docs/Web/Events/wheel#Browser_compatibility) and [Facebook's fixed-data-table normalizeWheel function](https://github.com/facebookarchive/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js#L125) yourself:

```javascript
addWheelListener(slider.$element[0], function (event) {
  var wheelNormalized = normalizeWheel(event);
  slider.applyForce(-wheelNormalized.pixelY / 4);
  slider.startAnimation();
  slider.dragEnd();
});

Where slider again is the Flickity instance.

UPDATE January 18th 2018: Added velocity normalization across browsers

desandro commented 5 years ago

People keep asking for this feature, so I'm re-opening.

Add a 👍 reaction to this issue if you would like to see this feature added. Do not add +1 comments — They will be deleted.

joebentaylor commented 5 years ago

For people who want to "hack in" mousewheel support, this was my approach:

import wheel from 'wheel';
import normalizeWheel from 'normalize-wheel';

wheel.addWheelListener(slider.$element[0], event => {
    const wheelNormalized = normalizeWheel(event);
    slider.applyForce(-wheelNormalized.pixelY / 4);
    slider.startAnimation();
    slider.dragEnd();
});

This uses Flickity's internal force, animation and drag functions to perform slider movement and therefore correctly handles slide indexes, freescroll, friction etc.

Note that this uses the pixelY property of the normalized wheel event. So it registers any vertical mouse scrolling as horizontal movement and does nothing with any horizontal mouse scrolling.

Also note that the slider.dragEnd() call fires the 'dragEnd' event with undefined parameters.

This code example uses NPM packages and arrow functions. If you don't want to use NPM packages or arrow functions, use the following piece of code after loading MDN's cross browser event listener for wheel events and Facebook's fixed-data-table normalizeWheel function yourself:

addWheelListener(slider.$element[0], function (event) {
  var wheelNormalized = normalizeWheel(event);
  slider.applyForce(-wheelNormalized.pixelY / 4);
  slider.startAnimation();
  slider.dragEnd();
});

Where slider again is the Flickity instance.

UPDATE January 18th 2018: Added velocity normalization across browsers

Anyway to do this without Jquery? Trying to codepen this and having no luck...

LuudJanssen commented 5 years ago

Anyway to do this without Jquery? Trying to codepen this and having no luck...

@joebentaylor, my hack isn't using jQuery if I'm correct. What issue are you encountering?

LeoSeyers commented 5 years ago

@LuudJanssen

i was looking for something like this for a full width/height slider, thank you! i did some user tests and found out people using safari on apple laptops were likely to scroll horizontally using the trackpad, here is the workaround I've came with

   const wheelNormalized = normalizeWheel(event);
          const highestValue = Math.abs(wheelNormalized.pixelY) >= Math.abs(wheelNormalized.pixelX) ?
            wheelNormalized.pixelY : wheelNormalized.pixelX * 0.75
          this.flkty.applyForce(-highestValue / 8);
          this.flkty.startAnimation();
          this.flkty.dragEnd();

in addition to your code, it performs a simple check for highest value on x or y axis scroll and use this value, i did not try it on iOs though

joebentaylor commented 5 years ago

Anyway to do this without Jquery? Trying to codepen this and having no luck...

@joebentaylor, my hack isn't using jQuery if I'm correct. What issue are you encountering?

I guess im confused as to what $element is

joebentaylor commented 5 years ago

After running the code i get this error TypeError: Cannot read property '0' of undefined

LeoSeyers commented 5 years ago

@joebentaylor adding $ before a variable is a naming convention for DOM selectors

if the method is not defined then probably the library is not exectuded the right way

finally you can replace slider.$element[0] by a simple DOM element query such as document.querySelector('.carousel') (adapt the code if you have multiple sliders, the original code assume that slider.$element is an available array, it wasn't in my case either)

joebentaylor commented 5 years ago

@LeoSeyers Im confused as to what element needs to be though, we are replacing slider with our slider name / reference, so whats $element supposed to reference? the slides?

joebentaylor commented 5 years ago

Ive finally got this working, however ive found the bounce to look awful, anybody got a solution to a very smooth finish?

LuudJanssen commented 5 years ago

@joebentaylor the bounce is controlled by the friction and the selectedAttraction. Try changing those values to get the result you're looking for. Otherwise you can look for the -wheelNormalized.pixelY / 4 and change the 4 to something that works for you.

@LeoSeyers thanks for the trackpad fix!

dejardine commented 5 years ago

Wondering if there was any progress on this feature? Literally the one thing stopping me from using flickity over swiper.

paullacour commented 4 years ago

if someone ever comes across this solution after importing flickity as a vue component with vue-flickity, here is the code that worked for me :) onInit() { const flickity = this.$refs.flickity.flickity() wheel.addWheelListener(flickity.element, event => { const wheelNormalized = normalizeWheel(event) flickity.applyForce(-wheelNormalized.pixelY / 4) flickity.startAnimation() flickity.dragEnd() }) }

yepecece commented 4 years ago

Can someone share a codepen with a working exemple of this? I am having trouble implementing it.

Thanks a lot

auspham commented 4 years ago

This is my version for those who use react-flickity. I use @LeoSeyers horizontal code:

 useLayoutEffect(() => {
    const flickity = slider.current.flkty;
    wheel.addWheelListener(flickity.element, event => {
      const wheelNormalized = normalizeWheel(event);
      const highestValue = Math.abs(wheelNormalized.pixelY) >= Math.abs(wheelNormalized.pixelX) ?
        wheelNormalized.pixelY : wheelNormalized.pixelX * 0.75
      flickity.applyForce(-highestValue / 8);
      flickity.startAnimation();
      flickity.dragEnd();
    })
 }, [slider])

return <Flickity ref={slider}>...</Flickity>

Does anyone have a scroll-like animation?

auspham commented 4 years ago

@joebentaylor this is my solution for a smooth finish, no bouncy:

  useLayoutEffect(() => {
    const flickity = slider.current.flkty;
    let range = {
      value: 0,
      max: 80,
      min: -80,
      step: 1,
      increase: function(number) {
        const threshold = this.max / flickity.slides.length;
        if(this.value < this.max) {
          this.value += this.step;
        }
        if(this.value >= threshold) {
          flickity.next();
          this.value -= threshold;
        }
      },
      decrease: function(number) {
        const threshold = this.max / flickity.slides.length;

        if(this.value > this.min) {
          this.value -= this.step;
        }
        if(this.value <= threshold) {
          flickity.previous();
          this.value += threshold;
        }
      }
    };
    wheel.addWheelListener(flickity.element, event => {
      const wheelNormalized = normalizeWheel(event);
      const direction = wheelNormalized.spinX * 100;
      direction > 0 ? range.increase(direction) : range.decrease(direction);
      flickity.startAnimation();
    })
  }, [slider])

modify the range max, min if you want it to be slower/faster.

jimmyadaro commented 4 years ago

Got something here ;)

$('.gallery').mousewheel(function(e) {
    e.preventDefault();
    var flkty = Flickity.data(this);

    if (!window.wheeling) {
        // console.log('start wheeling!');

        if(e.deltaX > 0 || e.deltaY < 0){
            flkty.next();
        } else if(e.deltaX < 0 || e.deltaY > 0){
            flkty.previous();
        }
    }

    clearTimeout(window.wheeling);
    window.wheeling = setTimeout(function() {
        // console.log('stop wheeling!');

        delete window.wheeling;

        // reset wheeldelta
        window.wheeldelta.x = 0;
        window.wheeldelta.y = 0;
    }, 250);

    window.wheeldelta.x += e.deltaFactor * e.deltaX;
    window.wheeldelta.y += e.deltaFactor * e.deltaY;
    if(window.wheeldelta.x > 500 || window.wheeldelta.y > 500 || window.wheeldelta.x < -500 || window.wheeldelta.y < -500){
        window.wheeldelta.x = 0;
        window.wheeldelta.y = 0;

        if(e.deltaX > 0 || e.deltaY < 0){
            flkty.next();
        } else if(e.deltaX < 0 || e.deltaY > 0){
            flkty.previous();
        }
    }

    // console.log(window.wheeldelta);

});

Found this approach over here: http://stackoverflow.com/questions/3515446/jquery-mousewheel-detecting-when-the-wheel-stops

This is a great approach, but you forgot to define the wheeldelta object, like so:

  var wheeldelta = {
    x: 0,
    y: 0
  };

  $('.gallery').mousewheel(function(e) {
    e.preventDefault();
    var flkty = Flickity.data(this);
    // ...

Otherwise it'll work but throw a lot of uncaught errors to the console.

rubengp99 commented 4 years ago

I want this feature too. Better experience than dragging when you're on Desktop

yepecece commented 3 years ago

Can anyone share a working solution with freescroll instead of flkty.next() and flkty.previous()?

Thanks

hooch commented 3 years ago

@yepececeI I found it was easier and more performant to start from scratch, simply making use of overflow: auto and scroll-snap in CSS. With some scroll-padding, carousels can be made to appear to scroll outside of the content margins as well. Buttons to scroll the carousel left and right took a bit more work.

jvwrx commented 2 years ago

Came across this recently and it saved my booty https://codepen.io/rniswonger/pen/ExjPryB (thx @rniswonger!)

ghost commented 2 years ago

☝️ this works but is not perfect, it scrolls the page with the slideshow as well, mouse events are not intercepted;

The feature must be in the core in 2022

andrei-dracea commented 11 months ago

@desandro any updates on this?

paubou commented 4 months ago

Any news?