react,js前端轮播图 Swipper Carousel实现无缝滚动效果

Carousel组件设计和具体代码实现

在前端中很多时候我们都需要实现图片自动播放然后图片一张一张从左往右滑动的一个效果。在组件库中我们称之为Carousel组件,我们从思路到代码来一起看看怎么封装一个属于自己的Carousel组件。

  1. 一个这样的组件首先想到的依然还是dom结构组成。
  2. 怎么保证无缝连接这个是这个组件的关键。
  3. 动画效果的实现,转场的过程中动画的衔接。

这个图大致就是我封装的时候的一个大致方向。每次移动的时候呢就是整个容器一起移动每次移动的距离每次就一个dom。
在这里插入图片描述
在可视区显示的就是当前能看到的图,超过这个可视区全部隐藏掉。在末尾我有复制了一个视图第一个元素的dom,这个就是实现无缝滚动的关键所在。当我们滑动到了最后一个视图,我们可以把整个容器的位置设置成最开始的位置,这样就可以完美实现最后一个视图和第一个视图的完美衔接了。
具体的代码逻辑就是先设置容器运动的动画正常执行,动画执行完成之后使用transition:none;然后修改transform:translate()的值。
由于没有动画效果所以这个操作对于用户是无感的。这个无缝的关键就是在动画效果取消,动画效果恢复。

Carousel代码实现逻辑
/*
 * @Date: 2023-08-23 14:06:45
 * @Auth: 463997479@qq.com
 * @LastEditors: 463997479@qq.com
 * @LastEditTime: 2023-08-29 14:26:30
 * @FilePath: \reactui\src\Carousel\index.tsx
 */
import { LeftCircleOutlined, RightCircleOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { debounce } from '../until';
type CarouselType = {
  children: any;
  autoplay?: boolean;
};

const Carousel: React.FC<CarouselType> = (props) => {
  const { children, autoplay = false } = props;
  const contentClass = classNames('carousel-content');
  const childrenRef = useRef();
  const [contentWidth, setContentWidth] = useState<number>();
  const [width, setWidth] = useState<number>(0);
  const [activeIndex, setActiveIndex] = useState<number>(0);
  const contentRef = useRef();
  const [contentStyle, setContentStyle] = useState<any>({});
  const [timer, setTimer] = useState<any>(null);
  const [flag, setFlag] = useState<boolean>(true);

  useLayoutEffect(() => {
    let index = activeIndex;
    //清除当前任务
    if (timer) {
      clearInterval(timer);
    }

    setTimer(
      setInterval(() => {
        if (flag) {
          return;
        }
        if (index <= children.length - 1) {
          index += 1;
          console.log(index);
          setContentStyle({
            transform: `translate3d(-${width * Math.abs(index)}px, 0px, 0px)`,
            transition: 'all .2s ease',
          });
          if (index === children.length) {
            index = 0;
            setTimeout(() => {
              setContentStyle({
                transform: `translate3d(-${width * index}px, 0px, 0px)`,
                transition: 'none',
              });
            }, 200);
          }

          setActiveIndex(index);
        }
      }, 2000),
    );
    return () => {
      clearInterval(timer);
    };
  }, [flag, width, activeIndex]);

  useEffect(() => {
    if (childrenRef.current) {
      const _width = childrenRef.current.offsetWidth;
      setContentWidth(_width * children.length);

      setWidth(_width);
    }
  }, []);

  const handlePre = debounce(() => {
    let index = activeIndex - 1;
    console.log(index);

    if (index === -1) {
      index = children.length - 1;
      setContentStyle({
        transform: `translate3d(-${width * (children.length + 1)}px, 0px, 0px)`,
        transition: 'none',
      });
      setTimeout(() => {
        setContentStyle({
          transform: `translate3d(-${width * index}px, 0px, 0px)`,
          transition: 'all 0.2s ease',
        });
      }, 0);
    } else {
      setContentStyle({
        transform: `translate3d(-${width * Math.abs(index)}px, 0px, 0px)`,
        transition: 'all 0.2s ease',
      });
    }

    setActiveIndex(index);
  }, 200);

  const handleNext = debounce(() => {
    let index = activeIndex + 1;
    setContentStyle({
      transform: `translate3d(-${width * index}px, 0px, 0px)`,
      transition: 'all 0.2s ease',
    });

    if (index === children.length) {
      index = 0;

      //到达末尾之后取消动画效果
      setTimeout(() => {
        setContentStyle({
          transform: `translate3d(-${width * index}px, 0px, 0px)`,
          transition: 'none',
        });
      }, 200);
    }
    setActiveIndex(index);
  }, 200);
  return (
    <>
      <div className={contentClass}>
        <div
          onMouseEnter={() => {
            if (autoplay) {
              setFlag(true);
            }
          }}
          onMouseLeave={() => {
            if (autoplay) {
              setFlag(false);
            }
          }}
          className="carousel-content-wrapper"
          ref={childrenRef}
        >
          <ul className="carousel-content-dots">
            {new Array(children.length).fill('').map((item, index) => (
              <li
                onClick={() => {
                  setContentStyle({
                    transform: `translate3d(-${width * index}px, 0px, 0px)`,
                    transition: 'all 0.2s ease',
                  });
                  setActiveIndex(index);
                }}
                className={
                  index === activeIndex ? 'carousel-content-dots-active' : ''
                }
                key={index}
              ></li>
            ))}
          </ul>
          <div className="carousel-step-content">
            <span
              onClick={() => handlePre()}
              className="carousel-step-content-left"
            >
              <LeftCircleOutlined />
            </span>

            <span
              onClick={() => handleNext()}
              className="carousel-step-content-right"
            >
              <RightCircleOutlined />
            </span>
          </div>

          <div
            style={{ ...contentStyle, width: contentWidth * 2 + 'px' }}
            className="carousel-content-list"
            ref={contentRef}
          >
           
            {React.Children.map([...children], (child) => {
              return (
                contentWidth &&
                React.cloneElement(child, {
                  className: 'carousel-content-item',
                  style: {
                    width: contentWidth / children.length + 'px',
                  },
                })
              );
            })}
            {React.Children.map([children[0]], (child) => {
              return (
                contentWidth &&
                React.cloneElement(child, {
                  className: 'carousel-content-item-clone',
                  style: {
                    width: contentWidth / children.length + 'px',
                  },
                })
              );
            })}
          </div>
        </div>
      </div>
    </>
  );
};
export default Carousel;

主要就是判断临界值在每个临界值的时候处理的逻辑有一点区别,其次就是在动画的取消还有动画的效果上面,什么时候应该滑动的时候不能加动画,什么时候有需要把动画效果加上。还有一个容器的大小还有各个子容器的大小都需要自己通过获取dom获取。

CSS
.carousel-content {
  width: 100%;
  .carousel-content-wrapper {
    width: 100%;
    height: 100%;

    position: relative;
    display: block;
    margin: 0;
    padding: 0;
    overflow: hidden;
    .carousel-content-dots {
      position: absolute;
      display: flex;
      z-index: 1;
      bottom: 10px;

      left: 50%;
      transform: translateX(-50%);

      li {
        cursor: pointer;
        position: relative;
        list-style: none;
        width: 16px;
        height: 3px;
        margin: 0 5px;
        box-sizing: border-box;
        transition: all 0.3s;
        padding: 0;
        background-color: #ffffff;
        opacity: 0.3;

        text-align: center;
        &::after {
          position: absolute;
          content: '';

          display: block;
        }
      }
      li.carousel-content-dots-active {
        width: 24px;
        opacity: 1;
      }
      li:hover {
        opacity: 0.5;
      }
    }
    .carousel-content-list {
      position: relative;
      top: 0;

      left: 0;
      display: block;
      &::after {
        display: block;
        clear: both;
        content: '';
      }
      .carousel-content-item {
        height: 100%;
        float: left;
      }
      .carousel-content-item-clone {
        height: 100%;
        float: left;
      }
    }
    .carousel-step-content {
      position: absolute;
      width: 100%;
      z-index: 2;
      top: 50%;
      transform: translateY(-50%);
      box-sizing: border-box;
      padding: 0 20px;
      font-size: 30px;

      opacity: 0;
      transition: all 0.3s;
      &::after {
        content: '';
        display: block;
        clear: both;
      }
      .carousel-step-content-left {
        color: #ffffff;
        float: left;
        opacity: 0.5;
        transition: all 0.3s;
        cursor: pointer;

        &:hover {
          opacity: 1;
        }
      }
      .carousel-step-content-right {
        color: #ffffff;
        float: right;
        opacity: 0.5;
        transition: all 0.3s;
        cursor: pointer;

        &:hover {
          opacity: 1;
        }
      }
    }
    &:hover {
      .carousel-step-content {
        opacity: 1;
      }
    }
  }
}

demo效果:
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React 实现效果可以通过以下步骤实现: 1. 创建轮组件:创建一个轮组件来包含所有的片和轮的逻辑。 2. 渲染片:在轮组件中定义一个数组,用来存放所有的片。使用 map 方法来遍历这个数组,渲染所有的片。 3. 定义状态:在轮组件中定义一个状态,用来记录当前显示的片的索引。初始值为0。 4. 轮逻辑:使用定时器和 setState 方法来实现逻辑。每隔一段时间,将当前显示的片索引加1,然后调用 setState 方法来更新状态。当索引大于等于片数组的长度时,将索引重置为0。 5. 添加控制按钮:可以添加左右控制按钮来控制轮的前进和后退。点击按钮时,调用 setState 方法来更新状态。 6. 完成轮组件:将所有的逻辑和 UI 结合起来,完成轮组件的编写。 以下是一个简单的轮组件代码示例: ```javascript import React, { useState, useEffect } from 'react'; const Carousel = ({ images }) => { const [activeIndex, setActiveIndex] = useState(0); useEffect(() => { const interval = setInterval(() => { setActiveIndex(activeIndex => (activeIndex + 1) % images.length); }, 3000); return () => clearInterval(interval); }, [images.length]); const onPrevClick = () => { setActiveIndex(activeIndex => (activeIndex - 1 + images.length) % images.length); }; const onNextClick = () => { setActiveIndex(activeIndex => (activeIndex + 1) % images.length); }; return ( <div className="carousel"> <div className="carousel-images"> {images.map((src, index) => ( <img key={index} src={src} alt={`Image ${index}`} className={index === activeIndex ? 'active' : ''} /> ))} </div> <button onClick={onPrevClick}>Prev</button> <button onClick={onNextClick}>Next</button> </div> ); }; export default Carousel; ``` 在这个例子中,使用了 useState 和 useEffect 钩子函数来实现状态和定时器的功能。渲染片时使用了 map 方法,根据当前显示的索引来给片添加 active 类名,从而实现高亮显示。左右控制按钮的点击事件分别调用 onPrevClick 和 onNextClick 方法,通过 setActiveIndex 方法来更新状态。最后,将所有的 UI 和逻辑结合起来,形成一个完整的轮组件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值