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

Swipes do not register in top half of portrait-oriented Carousel Card ('stack', and 'tinder') #262

Open
jordangrant opened this issue Feb 8, 2018 · 19 comments

Comments

@jordangrant
Copy link

@bd-arc,

Didn't follow the Template this time around due to time constraints, but I want to put this potential issue on your radar. It's a really weird one..

To reproduce:

  • I set up a Carousel with tall (portrait-oriented) cards.
  • I found that once the card exceeded a rectangular shape, the hitbox for swipes diminished.
  • Did not do much else by way of customization. Occurs with layouts 'stack', and 'tinder'

Works:
screen shot 2018-02-08 at 12 29 05 pm

Seeing Issue:
screen shot 2018-02-08 at 12 29 54 pm

Thanks for taking a look.

Reproduced on iOS device / simulator. Tested in Dev mode.

@bd-arc
Copy link
Contributor

bd-arc commented Feb 9, 2018

Hi @jordangrant,

Damn! I've encountered this issue before, but I was secretly hoping that this was a problem with something very specific in the provided example... Apparently not.

So far, this behavior seems linked to the carousel position in the viewport rather than having to do with the height of the item. It looks like approximately the top quarter of the screen is deaf to swipe events on the carousel. You can take a look at this screencast to see what I mean: https://giphy.com/gifs/xThtasOLFCD4A6pW6c

Note that I didn't have any issue on Android.

To be honest, I don't have the slightest clue so far about what's at stake :( I'm going to run a few tests based on the iOS/Android difference and see if I can get a better understanding of the issue.

Any idea on your end?

@bd-arc
Copy link
Contributor

bd-arc commented Feb 9, 2018

If I remove the following style rule, it works properly:

zIndex: carouselProps.data.length - index

But then, of course, the layout is completely messed up:
react-native-snap-carousel issue 262

A quick fix would be to use props scrollInterpolator and slideInterpolatedStyle to pass the Android 'stack' custom interpolation. The effect is going to be inverted, but at least there will be no need to specify a zIndex.


Here is the relevant code in case you want a quick fix for the 'stack' effect:

import Carousel, { getInputRangeFromIndexes } from 'react-native-snap-carousel';

function stackScrollInterpolator (index, carouselProps) {
    const range = [1, 0, -1, -2, -3];
    const inputRange = getInputRangeFromIndexes(range, index, carouselProps);
    const outputRange = range;
    return { inputRange, outputRange };
}

function stackAnimatedStyles (index, animatedValue, carouselProps) {
    const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth;
    const translateProp = carouselProps.vertical ? 'translateY' : 'translateX';

    const cardOffset = 18;
    const card1Scale = 0.9;
    const card2Scale = 0.8;

    const getTranslateFromScale = (index, scale) => {
        const centerFactor = 1 / scale * index;
        const centeredPosition = -Math.round(sizeRef * centerFactor);
        const edgeAlignment = Math.round((sizeRef - (sizeRef * scale)) / 2);
        const offset = Math.round(cardOffset * Math.abs(index) / scale);

        return centeredPosition - edgeAlignment - offset;
    };

    return {
        opacity: animatedValue.interpolate({
            inputRange: [-3, -2, -1, 0],
            outputRange: [0, 0.5, 0.75, 1],
            extrapolate: 'clamp'
        }),
        transform: [{
            scale: animatedValue.interpolate({
                inputRange: [-2, -1, 0, 1],
                outputRange: [card2Scale, card1Scale, 1, card1Scale],
                extrapolate: 'clamp'
            })
        }, {
            [translateProp]: animatedValue.interpolate({
                inputRange: [-3, -2, -1, 0, 1],
                outputRange: [
                    getTranslateFromScale(-3, card2Scale),
                    getTranslateFromScale(-2, card2Scale),
                    getTranslateFromScale(-1, card1Scale),
                    0,
                    sizeRef * 0.5
                ],
                extrapolate: 'clamp'
            })
        }]
    };
}

const myCarousel = (
    <Carousel
      scrollInterpolator={stackScrollInterpolator}
      slideInterpolatedStyle={stackAnimatedStyles}
      useScrollView={true} // <--- Use this for a better effect or disable it to get performance optimizations
    />
);

=> Note that you're going to get the Android effect on both platforms (inverted compared to the original iOS one).

react-native-snap-carousel stack layout android


Regarding the issue, I am left clueless. I don't understand why:

  • the zIndex rule randomly messes with the swipe events (sometimes you can swipe the 2-3 first items and then you need to swipe from a lower portion of the screen)
  • only the top part of the screen is affected
  • the default effect doesn't have any swipe issue even if we add the zIndex rule.

=> Any insight will be greatly appreciated!

@jordangrant
Copy link
Author

@bd-arc Thanks for the quick fix! No idea why the issue occurs, but this definitely helps me for the time being.

@donnes
Copy link

donnes commented Feb 28, 2018

Hello @bd-arc
How I implement the same fix but for the "Tinder" style?

@bd-arc
Copy link
Contributor

bd-arc commented Mar 2, 2018

Hi @donnes,

Here is the code in case you're facing the issue and need to fix the "Tinder" layout. I've just adapted the code you can find in /src/utils/animations.js.

import { Platform } from 'react-native';
import Carousel, { getInputRangeFromIndexes } from 'react-native-snap-carousel';

function stackScrollInterpolator (index, carouselProps) {
    const range = [1, 0, -1, -2, -3];
    const inputRange = getInputRangeFromIndexes(range, index, carouselProps);
    const outputRange = range;
    return { inputRange, outputRange };
}

function stackAnimatedStyles (index, animatedValue, carouselProps) {
    const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth;
    const mainTranslateProp = carouselProps.vertical ? 'translateY' : 'translateX';
    const secondaryTranslateProp = carouselProps.vertical ? 'translateX' : 'translateY';

    const cardOffset = 9;
    const card1Scale = 0.96;
    const card2Scale = 0.92;
    const card3Scale = 0.88;
    const peekingCardsOpacity = Platform.OS === 'android' ? 0.92 : 1;

    const getMainTranslateFromScale = (cardIndex, scale) => {
        const centerFactor = 1 / scale * cardIndex;
        return -Math.round(sizeRef * centerFactor);
    };

    const getSecondaryTranslateFromScale = (cardIndex, scale) => {
        return Math.round(cardOffset * Math.abs(cardIndex) / scale);
    };

    return {
        opacity: animatedValue.interpolate({
            inputRange: [-3, -2, -1, 0, 1],
            outputRange: [0, peekingCardsOpacity, peekingCardsOpacity, 1, 0],
            extrapolate: 'clamp'
        }),
        transform: [{
            scale: animatedValue.interpolate({
                inputRange: [-3, -2, -1, 0],
                outputRange: [card3Scale, card2Scale, card1Scale, 1],
                extrapolate: 'clamp'
            })
        }, {
            rotate: animatedValue.interpolate({
                inputRange: [0, 1],
                outputRange: ['0deg', '22deg'],
                extrapolate: 'clamp'
            })
        }, {
            [mainTranslateProp]: animatedValue.interpolate({
                inputRange: [-3, -2, -1, 0, 1],
                outputRange: [
                    getMainTranslateFromScale(-3, card3Scale),
                    getMainTranslateFromScale(-2, card2Scale),
                    getMainTranslateFromScale(-1, card1Scale),
                    0,
                    sizeRef * 1.1
                ],
                extrapolate: 'clamp'
            })
        }, {
            [secondaryTranslateProp]: animatedValue.interpolate({
                inputRange: [-3, -2, -1, 0],
                outputRange: [
                    getSecondaryTranslateFromScale(-3, card3Scale),
                    getSecondaryTranslateFromScale(-2, card2Scale),
                    getSecondaryTranslateFromScale(-1, card1Scale),
                    0
                ],
                extrapolate: 'clamp'
            })
        }]
    };
}

const myCarousel = (
    <Carousel
      scrollInterpolator={stackScrollInterpolator}
      slideInterpolatedStyle={stackAnimatedStyles}
      useScrollView={true} // <--- Use this for a better effect or disable it to get performance optimizations
    />
);

=> Note that, as for the stack layout, you'll get the Android effect on both platforms (inverted compared to the original iOS one).

react-native-snap-carousel tinder layout android

Hope this helps!

@nkrmr
Copy link

nkrmr commented Mar 5, 2018

Hello,

I'm facing the same problem, i can't swipe with the entire area of my cards because of zIndex issue on stack layout. I tried your solution but that revert my cards display and into vertical mode it's very not a good display and other think i can't use looping options. Is there another solution to swipe normaly and keep the all the functionalities??

@bd-arc
Copy link
Contributor

bd-arc commented Mar 5, 2018

@Soulso Unfortunately, I haven't been able to pinned down the root of the issue yet. Even though I fear a React Native bug, any insight that could point me in the relevant direction would be of great help!

I'm currently considering horrible workarounds like setting pointerEvents={'none'} for non-visible elements for example... I'll let you know if I find anything that works.

For now, unless you create a custom interpolation that doesn't require the zIndex trick (like this one), you may encounter the issue.

@nkrmr
Copy link

nkrmr commented Mar 5, 2018

Hello thanks for the answer, i hope you will found a solution.
How i can know that an element is non-visible?

@bd-arc
Copy link
Contributor

bd-arc commented Mar 5, 2018

@Soulso You can use the measure() method in conjunction with prop onSnapToItem(index). For example, you could check if index - 1, index and index + 1 are located in the viewport and then set pointerEvents to none for every item but those three, given that they are currently visible.

Or, you can simply set pointerEvents to auto for those three items, without relying on measure().

Be aware that:

  • it's a hack
  • measure() is asynchronous and its execution usually takes about 500 ms
  • there will almost certainly be side effects
  • I'm guessing that it can solve the issue, but I'm not sure yet.

Anyway, if you decide to give it a try, I'll be interested in your findings ;-)

@donnes
Copy link

donnes commented Mar 6, 2018

@bd-arc Thank you very much! It's fixed the problem on iOS using the Android style.

@brunoccc
Copy link

Not sure if this has been already reported, but I noticed that the area where swipe actions are not detected is actually occupied by one or more "ghost" cards.
If you inspect the layout (CMD+I) and tap in those areas, you can see that there is an invisible card partially overlapping the active one.
That said, given that the first two or three cards in the carousel seem to work well, I suppose that old cards that should be way off the screen are instead rendered in that area, messing with the click events.
Hope this could help in fixing or finding a workaround!

@radrich
Copy link

radrich commented Jun 22, 2018

I'm also experiencing the same issues. I thought I was going mad (hence the awe-inspiring background colors).
So if this helps at all: In the image below I'm on the 6th slide, and the ghost image is from the 2nd slide.
screen shot 2018-06-22 at 06 44 16

@brunoccc
Copy link

brunoccc commented Jun 22, 2018

Very interesting! However I confirm that the problem goes away if you use

removeClippedSubviews={false}

If you don't have too many cards it shouldn't be a big deal.

(EDIT: I've corrected my post. If you want a workaround, you need to set it to false)

@radrich
Copy link

radrich commented Jun 23, 2018

@brunoci removeClippedSubviews={true} doesn't help me 😢

@johncrisostomo
Copy link

I encountered an issue on iOS where I am displaying 3 carousels on 1 view and the third one is not showing up, removeClippedSubviews={true} fixed it.

@bd-arc
Copy link
Contributor

bd-arc commented May 21, 2019

In version 3.8.0 I've set removeClippedSubviews to false by default when using the 'stack' or 'tinder' layouts.

This should help!

@7dp
Copy link

7dp commented Aug 8, 2021

If I remove the following style rule, it works properly:

zIndex: carouselProps.data.length - index

But then, of course, the layout is completely messed up:
react-native-snap-carousel issue 262

A quick fix would be to use props scrollInterpolator and slideInterpolatedStyle to pass the Android 'stack' custom interpolation. The effect is going to be inverted, but at least there will be no need to specify a zIndex.

Here is the relevant code in case you want a quick fix for the 'stack' effect:

import Carousel, { getInputRangeFromIndexes } from 'react-native-snap-carousel';

function stackScrollInterpolator (index, carouselProps) {
    const range = [1, 0, -1, -2, -3];
    const inputRange = getInputRangeFromIndexes(range, index, carouselProps);
    const outputRange = range;
    return { inputRange, outputRange };
}

function stackAnimatedStyles (index, animatedValue, carouselProps) {
    const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth;
    const translateProp = carouselProps.vertical ? 'translateY' : 'translateX';

    const cardOffset = 18;
    const card1Scale = 0.9;
    const card2Scale = 0.8;

    const getTranslateFromScale = (index, scale) => {
        const centerFactor = 1 / scale * index;
        const centeredPosition = -Math.round(sizeRef * centerFactor);
        const edgeAlignment = Math.round((sizeRef - (sizeRef * scale)) / 2);
        const offset = Math.round(cardOffset * Math.abs(index) / scale);

        return centeredPosition - edgeAlignment - offset;
    };

    return {
        opacity: animatedValue.interpolate({
            inputRange: [-3, -2, -1, 0],
            outputRange: [0, 0.5, 0.75, 1],
            extrapolate: 'clamp'
        }),
        transform: [{
            scale: animatedValue.interpolate({
                inputRange: [-2, -1, 0, 1],
                outputRange: [card2Scale, card1Scale, 1, card1Scale],
                extrapolate: 'clamp'
            })
        }, {
            [translateProp]: animatedValue.interpolate({
                inputRange: [-3, -2, -1, 0, 1],
                outputRange: [
                    getTranslateFromScale(-3, card2Scale),
                    getTranslateFromScale(-2, card2Scale),
                    getTranslateFromScale(-1, card1Scale),
                    0,
                    sizeRef * 0.5
                ],
                extrapolate: 'clamp'
            })
        }]
    };
}

const myCarousel = (
    <Carousel
      scrollInterpolator={stackScrollInterpolator}
      slideInterpolatedStyle={stackAnimatedStyles}
      useScrollView={true} // <--- Use this for a better effect or disable it to get performance optimizations
    />
);

=> Note that you're going to get the Android effect on both platforms (inverted compared to the original iOS one).

react-native-snap-carousel stack layout android

Regarding the issue, I am left clueless. I don't understand why:

  • the zIndex rule randomly messes with the swipe events (sometimes you can swipe the 2-3 first items and then you need to swipe from a lower portion of the screen)
  • only the top part of the screen is affected
  • the default effect doesn't have any swipe issue even if we add the zIndex rule.

=> Any insight will be greatly appreciated!

@bd-arc can i make the inverted version from this effect on "tinder" layout on both android & ios?

@dohooo

This comment was marked as spam.

@MichaelRay23
Copy link

If I remove the following style rule, it works properly:

zIndex: carouselProps.data.length - index

But then, of course, the layout is completely messed up: react-native-snap-carousel issue 262

A quick fix would be to use props scrollInterpolator and slideInterpolatedStyle to pass the Android 'stack' custom interpolation. The effect is going to be inverted, but at least there will be no need to specify a zIndex.

Here is the relevant code in case you want a quick fix for the 'stack' effect:

import Carousel, { getInputRangeFromIndexes } from 'react-native-snap-carousel';

function stackScrollInterpolator (index, carouselProps) {
    const range = [1, 0, -1, -2, -3];
    const inputRange = getInputRangeFromIndexes(range, index, carouselProps);
    const outputRange = range;
    return { inputRange, outputRange };
}

function stackAnimatedStyles (index, animatedValue, carouselProps) {
    const sizeRef = carouselProps.vertical ? carouselProps.itemHeight : carouselProps.itemWidth;
    const translateProp = carouselProps.vertical ? 'translateY' : 'translateX';

    const cardOffset = 18;
    const card1Scale = 0.9;
    const card2Scale = 0.8;

    const getTranslateFromScale = (index, scale) => {
        const centerFactor = 1 / scale * index;
        const centeredPosition = -Math.round(sizeRef * centerFactor);
        const edgeAlignment = Math.round((sizeRef - (sizeRef * scale)) / 2);
        const offset = Math.round(cardOffset * Math.abs(index) / scale);

        return centeredPosition - edgeAlignment - offset;
    };

    return {
        opacity: animatedValue.interpolate({
            inputRange: [-3, -2, -1, 0],
            outputRange: [0, 0.5, 0.75, 1],
            extrapolate: 'clamp'
        }),
        transform: [{
            scale: animatedValue.interpolate({
                inputRange: [-2, -1, 0, 1],
                outputRange: [card2Scale, card1Scale, 1, card1Scale],
                extrapolate: 'clamp'
            })
        }, {
            [translateProp]: animatedValue.interpolate({
                inputRange: [-3, -2, -1, 0, 1],
                outputRange: [
                    getTranslateFromScale(-3, card2Scale),
                    getTranslateFromScale(-2, card2Scale),
                    getTranslateFromScale(-1, card1Scale),
                    0,
                    sizeRef * 0.5
                ],
                extrapolate: 'clamp'
            })
        }]
    };
}

const myCarousel = (
    <Carousel
      scrollInterpolator={stackScrollInterpolator}
      slideInterpolatedStyle={stackAnimatedStyles}
      useScrollView={true} // <--- Use this for a better effect or disable it to get performance optimizations
    />
);

=> Note that you're going to get the Android effect on both platforms (inverted compared to the original iOS one).

react-native-snap-carousel stack layout android react-native-snap-carousel stack layout android

Regarding the issue, I am left clueless. I don't understand why:

  • the zIndex rule randomly messes with the swipe events (sometimes you can swipe the 2-3 first items and then you need to swipe from a lower portion of the screen)
  • only the top part of the screen is affected
  • the default effect doesn't have any swipe issue even if we add the zIndex rule.

=> Any insight will be greatly appreciated!

I want right stack swipe direction, what should I need to change in animation file?

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

No branches or pull requests

10 participants