简体   繁体   中英

how can I make a custom carousel or image slide in react native without using any npm packages

I am making a custom image carousel or an image slider in react native without using any packages, the issue is that the dot indicators on the bottom are changing fine after 3 second intervals but the image doesn't slides automatically. There is some minor issue which I am unable to find. If you can help in getting this fixed without changing much of the code it would be really nice. thanks in advance.

Here is the full component code.

import React, {useEffect, useState, useRef} from 'react';
import {StyleSheet, ScrollView, View, Dimensions, Text} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {Image} from 'react-native-elements';

const HomeCarousel = () => {
  const [dimension, setDimension] = useState(Dimensions.get('window'));
  const [selectedIndex, setSelectedIndex] = useState(0);

  const scrollRef = useRef();

  const onChange = () => {
    setDimension(Dimensions.get('window'));
  };

  useEffect(() => {
    Dimensions.addEventListener('change', onChange);
    return () => {
      Dimensions.removeEventListener('change', onChange);
    };
  });

  useEffect(() => {
    setInterval(() => {
      setSelectedIndex(prevSelectedIndex =>
        prevSelectedIndex === carouselImages.length - 1
          ? 0
          : prevSelectedIndex + 1,
      );
      () => {
        scrollRef.current.scrollTo({
          animated: true,
          y: 0,
          x: dimension.width * selectedIndex,
        });
      };
    }, 3000);
  }, []);

  const carouselImages = [
    {url: 'https://i.ibb.co/FDwNR9d/img1.jpg'},
    {url: 'https://i.ibb.co/7G5qqGY/1.jpg'},
    {url: 'https://i.ibb.co/Jx7xqf4/pexels-august-de-richelieu-4427816.jpg'},
    {url: 'https://i.ibb.co/GV08J9f/pexels-pixabay-267202.jpg'},
    {url: 'https://i.ibb.co/sK92ZhC/pexels-karolina-grabowska-4210860.jpg'},
  ];

  const setIndex = event => {
    let viewSize = event.nativeEvent.layoutMeasurement.width;
    let contentOffset = event.nativeEvent.contentOffset.x;
    let carouselIndex = Math.floor(contentOffset / viewSize);
    setSelectedIndex(carouselIndex);
  };

  return (
    <View style={{width: dimension.width}}>
      <ScrollView
        horizontal
        ref={scrollRef}
        onMomentumScrollEnd={setIndex}
        showsHorizontalScrollIndicator={false}
        pagingEnabled>
        {carouselImages.map((value, key) => (
          <Image
            source={{uri: `${value.url}`}}
            style={{width: dimension?.width, height: 250, resizeMode: 'cover'}}
            PlaceholderContent={<ActivityIndicator />}
          />
        ))}
      </ScrollView>
      <View
        style={{
          flexDirection: 'row',
          position: 'absolute',
          bottom: 0,
          alignSelf: 'center',
        }}>
        {carouselImages.map((val, key) => (
          <Text
            key={key}
            style={key === selectedIndex ? {color: 'white'} : {color: '#888'}}>
            ⬤
          </Text>
        ))}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({});

export default HomeCarousel;

Your images aren't being update because of 2 things:

  1. You're setting the update in an anonymous function which isn't being called:
// You're just defining the function here and not actually calling it
() => {
  scrollRef.current.scrollTo({
    animated: true,
    y: 0,
    x: dimension.width * selectedIndex,
  });
};
  1. Your useEffect doesn't have any dependencies, which means its callback won't be changed, and therefore the selectedIndex inside of it will always be 0:
useEffect(() => {
  setInterval(() => {
    setSelectedIndex(prevSelectedIndex =>
      prevSelectedIndex === carouselImages.length - 1
        ? 0
        : prevSelectedIndex + 1,
    );
    () => {
      scrollRef.current.scrollTo({
        animated: true,
        y: 0,
        // Since there are no dependencies, this callback won't ever be changed and selectedIndex will always be 0
        x: dimension.width * selectedIndex,
      });
    };
  }, 3000);
  // You have no dependencies here
}, []);

My suggestions for improvements/fixes would be:

  1. Extract the function to update the slides out of useEffect callback and apply a useCallback hook with selectedIndex dependency. You should also calculate the value of the new index outside of the setSelectedIndex hook, because setState/useState doesn't update immediately :
// You should also use an intervalId so that you can clear the interval/reset when needed
let intervalId = null;

const HomeCarousel = () => {
  // ... rest of the HomeCarousel component

  const onSlideChange = useCallback(() => {
    // Calculate newIndex here and use it to update your state and to scroll to the new slide
    const newIndex = selectedIndex === carouselImages.length - 1 ? 0 : selectedIndex + 1;

    setSelectedIndex(newIndex);

    scrollRef?.current?.scrollTo({
      animated: true,
      y: 0,
      x: dimension.width * newIndex,
    });
  }, [selectedIndex]);

  const startInterval = useCallback(() => {
    intervalId = setInterval(onSlideChange, 3000);
  }, [onSlideChange]);

  useEffect(() => {
    startInterval();

    return () => {
      // Clear the interval when component unmounts, otherwise you could have memory leaks
      clearInterval(intervalId);
    };
  }, [onSlideChange]);  

  // ... rest of the HomeCarousel component
};
  1. Another thing I would suggest you to make this carousel complete is to clear the interval/stop the automatic sliding as soon as the user starts sliding, otherwise it would cause weird behavior and slides getting out of control. You can restart the automatic sliding as soon as the user has finished his sliding action:
const onTouchStart = () => {
  // As soon as the user touches the slide, stop the automatic sliding
  clearInterval(intervalId);
};

const onTouchEnd = () => {
  // As soon as the user stops touching the slide, releases it, start the automatic sliding again
  startInterval();
};

return (
  <ScrollView
    // ... Other scrollview props
    onTouchStart={onTouchStart}
    onTouchEnd={onTouchEnd}>
    {/* ... Rest of the component */}

 const carouselImages = [ carousel1, carousel2, carousel3, carousel4, carousel5 ]; const Carousel = () => { const [current, setCurrent] = useState(0); useEffect(() => { const next = (current + 1) % carouselImages.length; const id = setTimeout(() => setCurrent(next), 3000); return () => clearTimeout(id); }, [current]); return ( <div className="banner"> <div className="slider"> <img src={carouselImages[current]} alt="carousel" /> </div> <div className="overlay"></div> </div> );

Here is the full working code.

import React, {useEffect, useState, useRef, useCallback} from 'react';
import {StyleSheet, ScrollView, View, Dimensions, Text} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {Image} from 'react-native-elements';

const HomeCarousel = () => {
  const [dimension, setDimension] = useState(Dimensions.get('window'));
  const [selectedIndex, setSelectedIndex] = useState(0);

  const scrollRef = useRef();
  let intervalId = null;

  const onChange = () => {
    setDimension(Dimensions.get('window'));
  };

  useEffect(() => {
    Dimensions.addEventListener('change', onChange);
    return () => {
      Dimensions.removeEventListener('change', onChange);
    };
  });

  const onSlideChange = useCallback(() => {
    // Calculate newIndex here and use it to update your state and to scroll to the new slide
    const newIndex =
      selectedIndex === carouselImages.length - 1 ? 0 : selectedIndex + 1;

    setSelectedIndex(newIndex);

    scrollRef?.current?.scrollTo({
      animated: true,
      y: 0,
      x: dimension.width * newIndex,
    });
  }, [selectedIndex]);

  const startInterval = useCallback(() => {
    intervalId = setInterval(onSlideChange, 3000);
  }, [onSlideChange]);

  useEffect(() => {
    startInterval();

    return () => {
      // Clear the interval when component unmounts, otherwise you could have memory leaks
      clearInterval(intervalId);
    };
  }, [onSlideChange]);

  const onTouchStart = () => {
    // As soon as the user touches the slide, stop the automatic sliding
    clearInterval(intervalId);
  };

  const onTouchEnd = () => {
    // As soon as the user stops touching the slide, releases it, start the automatic sliding again
    startInterval();
  };

  const carouselImages = [
    {url: 'https://i.ibb.co/FDwNR9d/img1.jpg'},
    {url: 'https://i.ibb.co/7G5qqGY/1.jpg'},
    {url: 'https://i.ibb.co/Jx7xqf4/pexels-august-de-richelieu-4427816.jpg'},
    {url: 'https://i.ibb.co/GV08J9f/pexels-pixabay-267202.jpg'},
    {url: 'https://i.ibb.co/sK92ZhC/pexels-karolina-grabowska-4210860.jpg'},
  ];

  const setIndex = event => {
    let viewSize = event.nativeEvent.layoutMeasurement.width;
    let contentOffset = event.nativeEvent.contentOffset.x;
    let carouselIndex = Math.floor(contentOffset / viewSize);
    setSelectedIndex(carouselIndex);
  };

  return (
    <View style={{width: dimension.width}}>
      <ScrollView
        horizontal
        ref={scrollRef}
        onMomentumScrollEnd={setIndex}
        showsHorizontalScrollIndicator={false}
        onTouchStart={onTouchStart}
        onTouchEnd={onTouchEnd}
        pagingEnabled>
        {carouselImages.map((value, key) => (
          <Image
            source={{uri: `${value.url}`}}
            style={{width: dimension?.width, height: 250, resizeMode: 'cover'}}
            PlaceholderContent={<ActivityIndicator />}
          />
        ))}
      </ScrollView>
      <View
        style={{
          flexDirection: 'row',
          position: 'absolute',
          bottom: 0,
          alignSelf: 'center',
        }}>
        {carouselImages.map((val, key) => (
          <Text
            key={key}
            style={key === selectedIndex ? {color: 'white'} : {color: '#888'}}>
            ⬤
          </Text>
        ))}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({});

export default HomeCarousel;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM