react custom carousel

 

 ./carousel.module.css:

.container {
    overflow: hidden;
    position: relative;
}

.inner {
    white-space: nowrap;
    transition: transform 0.3s;
    width: 100%;
    height: 100%;
}

.carousel_item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
}
.carousel_info{
    width: 100%;
    height: 100%;
}

.carousel_info_image {
    width: 100%;
    height: 100%;
    border-radius: 20px
}

.carousel_info_text {
    animation: info_text 2s ease;
    animation-fill-mode: forwards;
    color: white;
    position: absolute;
    text-align: center;
    width: 250px;
    top: 100px;
}

.carousel_info_title {
    font-size: 18px;
    width: 100%;
}

.carousel_info_content {
    font-size: 15px;
    width: 100%;
    white-space:normal;
    word-break:break-all;
    line-height: 40px;
}


.loading {
    position: absolute;
    bottom: 50px;
    display: flex;
    align-items: center;
    margin-bottom: 10px;
    width: 100%;
    left: 0;
    flex-direction: row;
}

.indicator_outer {
    width: 90px;
    height: 7px;
    background-color: #d4d4d4;
    margin-left: 20px;
    border-radius: 5px;
}

.indicator_inside {
    height: 100%;
    border-radius: 5px;
    animation-fill-mode: forwards;
    animation-name: progressBar;
    animation-iteration-count: infinite;
}


@keyframes info_text {
    0% {
        transform: translateX(0px);
        -webkit-transform: translateX(0px);
    }

    100% {
        transform: translateX(10%);

        -webkit-transform: translateX(10%);
    }
}


@keyframes progressBar {
    0% {
        width: 0%;
    }

    100% {
        width: 100%;
    }
}

 carousel.js:

import React, { useState, useEffect } from "react";
import style from "./carousel.module.css";

/**
 * @param {children} children ReactNode
 * @param {styles} styles 样式
 * @returns 轮播图 单项
 */
export const CarouselItem = ({
  children = React.createElement("div"),
  styles = {},
}) => {
  return (
    <div
      className={style.carousel_item}
      style={
   
   { ...styles }}>
      {children}
    </div>
  );
};

/**
 * @param {image} image 图片
 * @returns 轮播图 主体
 */
export const CarouselInfo = ({ image = "", title="", content="" }) => {
  return (
    <div className={style.carousel_info}>
      <img src={image} alt="Jay" className={style.carousel_info_image} />
      <div className={style.carousel_info_text}>
        <div className={style.carousel_info_title}>{title}</div>
        <div className={style.carousel_info_content}>{content}</div>
      </div>
    </div>
  );
};

/**
 * @param {children} children ReactNode
 * @param {switchingTime} switchingTime 间隔时间 默认3秒 以毫秒为单位 3000ms = 3s
 * @returns 轮播图 容器
 */
const Carousel = ({
  children = React.createElement("div"),
  switchingTime = 3000,
  height="500px",
  width="800px",
  dot_max_width="100px",
  dot_min_width="50px",
}) => {
  const time = ((switchingTime % 60000) / 1000).toFixed(0); // 将毫秒转换为秒
  const [activeIndex, setActiveIndex] = useState(0); // 对应索引

  /**
   * 更新索引
   * @param {newIndex} newIndex 更新索引
   */
  const onUpdateIndex = (newIndex) => {
    if (newIndex < 0) {
      newIndex = React.Children.count(children) - 1;
    } else if (newIndex >= React.Children.count(children)) {
      newIndex = 0;
    }
    setActiveIndex(newIndex);
    replayAnimations();
  };

  /**
   * 重置动画
   */
  const replayAnimations = () => {
    document.getAnimations().forEach((anim) => {
      anim.cancel();
      anim.play();
    });
  };

  /**
   * 底部加载条点击事件
   * @param {index} index 跳转索引
   */
  const onClickCarouselIndex = (index) => {
    onUpdateIndex(index);
    replayAnimations();
  };

  useEffect(() => {
    const interval = setInterval(() => {
      onUpdateIndex(activeIndex + 1);
    }, switchingTime);

    return () => {
      if (interval) {
        clearInterval(interval);
      }
    };
  });

  return (
    <div className={style.container} style={
   
   {height:height,width:width}}>
      <div
        className={style.inner}
        style={
   
   { transform: `translateX(-${activeIndex * 100}%)` }}>
        {React.Children.map(children, (child) => {
          return React.cloneElement(child);
        })}
      </div>

      <div className={style.loading}>
        {React.Children.map(children, (child, index) => {
          return (
            <div
              className={style.indicator_outer}
              onClick={() => onClickCarouselIndex(index)}
              style={
   
   {
                width: index === activeIndex ? dot_max_width : dot_min_width,
              }}
            >
              <div
                className={style.indicator_inside}
                style={
   
   {
                  animationDuration: index === activeIndex ? `${time}s` : "0s",
                  backgroundColor: index === activeIndex ? "#FFFFFF" : null,
                }}
              />
            </div>
          );
        })}
      </div>
    </div>
  );
};

export default Carousel;

 use:

import React from 'react'

import Carousel, { CarouselItem, CarouselInfo } from "./carousel";

// 轮播图数据
const info = [
  {
    id: 1,
    image: "https://img2.baidu.com/it/u=138111414,3014715895&fm=253&fmt=auto&app=138&f=JPEG?w=1000&h=300",
    title:"岐王李茂贞",
    content:"轮子哥测试文本内容轮子哥测试文本内容轮子哥测试文本内容"
  },
  {
    id: 2,
    image: "https://img1.baidu.com/it/u=652481803,1746310969&fm=253&fmt=auto&app=138&f=JPEG?w=1000&h=300",
    title:"我们的船长可是要立志成为【海王】的男人",
    content:"轮子哥测试文本内容轮子哥测试文本内容轮子哥测试文本内容"
  },
  {
    id: 3,
    image: "https://img0.baidu.com/it/u=84299672,3744735177&fm=253&fmt=auto&app=138&f=JPEG?w=1000&h=300",
    title:"魁拔!魁拔!",
    content:"轮子哥测试文本内容轮子哥测试文本内容轮子哥测试文本内容"
  },
  {
    id: 4,
    image: "https://img2.baidu.com/it/u=1544206047,4026750196&fm=253&fmt=auto&app=138&f=JPEG?w=1000&h=300",
    title:"我白小纯,果真是天道之人",
    content:"轮子哥测试文本内容轮子哥测试文本内容轮子哥测试文本内容"
  },

];

export default function Test() {
  return (
    <Carousel switchingTime={2000} width="800px" height="450px">
    {info?.map((item) => {
      return (
        <CarouselItem key={item.id}>
          <CarouselInfo image={item.image} title={item.title} content={item.content}/>
        </CarouselItem>
      );
    })}
  </Carousel>
  )
}

The above is written in h5


Slightly different from the above, the parameters need to be changed. Because the applet does not have a document, the animation of the specified node can only be canceled and re-executed by changing the animation style binding. If you have a better solution, please let me know

Use ts, and the writing method of applets can be transplanted:

 Carousel.js:

import React, { useState, useEffect, ReactElement } from "react";
import style from "./Carousel.module.scss";

/**
 * 轮子哥
 */

interface CarouselItem {
  children: ReactElement;
  styles?: object;
}

export const CarouselItem: any = React.memo((props: CarouselItem) => {
  return (
    <div
      className={style.carousel_item}
      style={
   
   { ...props.styles }}>
      {props.children}
    </div>
  );
});



interface CarouselInfo {
  image: string,
  title?: string,
  content?: string
}

export const CarouselInfo = React.memo((props: CarouselInfo) => {

  const [isChange, setChange] = useState(false)

  useEffect(() => {
    const interval = setInterval(() => {
      setChange(!isChange);
    }, 5000);

    return () => {
      if (interval) {
        clearInterval(interval);
      }
    };
  });

  return (
    <div className={style.carousel_info}>
      <img src={props.image} alt="Jay" className={style.carousel_info_image} />
      <div className={isChange ? style.carousel_info_text : style.carousel_info_text_2}>
        <div className={style.carousel_info_title}>{props.title}</div>
        <div className={style.carousel_info_content}>{props.content}</div>
      </div>
    </div>
  );

});


interface Carousel  {
  children :ReactElement,
  switchingTime : number,
  height ?: string,
  width ?: string,
  dot_max_width ?: string,
  dot_min_width ?: string,
}

const Carousel:any =React.memo( (props:Carousel) => {
  const time = (props.switchingTime % (1000 * 60)) / 1000; // 将毫秒转换为秒
  const [activeIndex, setActiveIndex] = useState(0); // 对应索引
  const [isNewPlay, setIsNewPlay] = useState(true); // 对应索引

  const onUpdateIndex = (newIndex) => {
    if (newIndex < 0) {
      newIndex = React.Children.count(props.children) - 1;
    } else if (newIndex >= React.Children.count(props.children)) {
      newIndex = 0;
    }
    setIsNewPlay(!isNewPlay);
    setActiveIndex(newIndex);
    //replayAnimations();
  };

  // const replayAnimations = () => {
  //   document.getAnimations().forEach((anim) => {
  //     anim.cancel();
  //     anim.play();
  //   });
  // };

  const onClickCarouselIndex = (index) => {
    onUpdateIndex(index);
    setIsNewPlay(!isNewPlay)
    //replayAnimations();
  };

  useEffect(() => {
    const interval = setInterval(() => {
      onUpdateIndex(activeIndex + 1);
    }, props.switchingTime);

    return () => {
      if (interval) {
        clearInterval(interval);
      }
    };
  });

  return (
    <div className={style.container} style={
   
   { height: props.height, width: props.width }}>
      <div
        className={style.inner}
        style={
   
   { transform: `translateX(-${activeIndex * 100}%)` }}>
        {React.Children.map(props.children, (child) => {
          return React.cloneElement(child);
        })}
      </div>

      <div className={style.loading}>
        {React.Children.map(props.children, (index: any, child) => {
          return (
            <div
              className={style.indicator_outer}
              style={
   
   {
                width: child === activeIndex ? props.dot_max_width : props.dot_min_width,
              }}>
              <div
                className={isNewPlay ? style.indicator_inside : style.indicator_inside_2}
                onClick={() => onClickCarouselIndex(child)}
                style={
   
   {
                  animationDuration: child === activeIndex ? `${time}s` : "0s",
                  backgroundColor: child === activeIndex ? "#FFFFFF" : "",
                }}
              />
            </div>
          );
        })}
      </div>
    </div>
  );
});

Carousel.defaultProps ={
  switchingTime : 3000,
  height : "500px",
  width : "800px",
  dot_max_width : "40px",
  dot_min_width : "20px",
}

export default Carousel;

 Carousel.module.less:

.container {
    overflow: hidden;
    position: relative;
}

.inner {
    white-space: nowrap;
    transition: transform 0.3s;
    width: 100%;
    height: 100%;
}

.carousel_item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
}
.carousel_info{
    width: 100%;
    height: 100%;
}

.carousel_info_image {
    width: 100%;
    height: 100%;
    border-radius: 20px
}

.carousel_info_text {
    animation: info_text 2s ease;
    animation-fill-mode: forwards;
    color: white;
    position: absolute;
    text-align: center;
    width: 250px;
    top: 100px;
}

.carousel_info_title {
    font-size: 18px;
    width: 100%;
}

.carousel_info_content {
    font-size: 15px;
    width: 100%;
    white-space:normal;
    word-break:break-all;
    line-height: 40px;
}


.loading {
    position: absolute;
    bottom: 50px;
    display: flex;
    align-items: center;
    margin-bottom: 10px;
    width: 100%;
    left: 0;
    flex-direction: row;
}

.indicator_outer {
    width: 90px;
    height: 7px;
    background-color: #d4d4d4;
    margin-left: 20px;
    border-radius: 5px;
}

.indicator_inside {
    height: 100%;
    border-radius: 5px;
    animation-fill-mode: forwards;
    animation-name: progressBar;
    animation-iteration-count: infinite;
}

.indicator_inside_2 {
    height: 100%;
    border-radius: 5px;
    animation-fill-mode: forwards;
    animation-name: progressBar_2;
    animation-iteration-count: infinite;
}

@keyframes info_text {
    0% {
        transform: translateX(0px);
        -webkit-transform: translateX(0px);
    }

    100% {
        transform: translateX(10%);

        -webkit-transform: translateX(10%);
    }
}

@keyframes progressBar {
    0% {
        width: 0%;
    }

    100% {
        width: 100%;
    }
}

@keyframes progressBar_2 {
    0% {
        width: 0%;
    }

    100% {
        width: 100%;
    }
}

use:

// import { useState } from 'react'
import style from '../style/Carousel.module.less'
// import { Swiper, SwiperItem } from '@tarojs/components'
// import images from '../resources'

import Carousels, { CarouselItem, CarouselInfo } from "./Carousel";

// 轮播图数据
const info = [
  {
    id: 1,
    image: "https://img2.baidu.com/it/u=138111414,3014715895&fm=253&fmt=auto&app=138&f=JPEG?w=1000&h=300",
    title:"岐王李茂贞",
    content:"轮子哥测试文本内容轮子哥测试文本内容轮子哥测试文本内容"
  },
  {
    id: 2,
    image: "https://img1.baidu.com/it/u=652481803,1746310969&fm=253&fmt=auto&app=138&f=JPEG?w=1000&h=300",
    title:"我们的船长可是要立志成为【海王】的男人",
    content:"轮子哥测试文本内容轮子哥测试文本内容轮子哥测试文本内容"
  },
  {
    id: 3,
    image: "https://img0.baidu.com/it/u=84299672,3744735177&fm=253&fmt=auto&app=138&f=JPEG?w=1000&h=300",
    title:"魁拔!魁拔!",
    content:"轮子哥测试文本内容轮子哥测试文本内容轮子哥测试文本内容"
  },
  {
    id: 4,
    image: "https://img2.baidu.com/it/u=1544206047,4026750196&fm=253&fmt=auto&app=138&f=JPEG?w=1000&h=300",
    title:"我白小纯,果真是天道之人",
    content:"轮子哥测试文本内容轮子哥测试文本内容轮子哥测试文本内容"
  },

];

export default function Carousel() {

    return (
        <div className={style.Carousel}>

            <Carousels switchingTime={5000} width="100%" height="7rem">
                {info?.map((item) => {
                    return (
                        <CarouselItem key={item.id}>
                            <CarouselInfo image={item.image} title={item.title} content={item.content} />
                        </CarouselItem>
                    );
                })}
            </Carousels>
        </div>
    )
}

Guess you like

Origin blog.csdn.net/qq_46149597/article/details/129405343