使用react封装图片轮播效果

实现图片轮播的基本原理

  • 有一个视窗容器显示当前图片内容,容器之外的内容都被隐藏
  • 在视窗容器中,有一个包含一组图片的子容器改变其水平或者垂直方向的,形成轮播
轮播图位置变化示意图

上图的蓝色框表示视窗容器,蓝色框之外的区域是被隐藏的。当橙色区域的left或者tranlateX为-200时,可视范围由1变成2。此时轮播图为向右翻页。

<div class="container"> <!--视窗容器-->
    <div class="slider-content" style="left: -200px;"><!--图片容器-->
        <div class="slider-item">1</div>
        <div class="slider-item">2</div>
        <div class="slider-item">3</div>
    </div>
</div>

接下来得思考轮播图应该具有的哪些功能点:

  • 自动播放
  • 无缝循环播放
  • 点击按钮翻页
  • 拖动翻页
轮播图的自动播放

自动播放可以理解为定时执行,就是间隔几秒去修改容器的位置。因此使用setInterval或者setTimeout即可。

以下代码简单是实现了自动轮播的效果。

var content = document.querySelector('.slider-content');

class Slider  {
   constructor(ele){
    this.ele = ele;
    this.width = this.ele.parentNode.offsetWidth; // 获取宽度
    this.length = document.querySelectorAll('.slider-item').length; 
    this.animate(); 
   }

   move() {
    let ele = this.ele;
    if(parseInt(ele.style.left) > -(this.length*this.width)) {
        // 设置容器改变的left值
        ele.style.left = parseInt(ele.style.left) - this.width +'px';
    }else{
        ele.style.left = 0;
    }   
  }

  animate() {
    setInterval(this.move.bind(this),2000)
  }
}

var box = new Slider(content);

观察上面的效果可以发现,当播放到最后一个图片,再回到第一页时,出现了空白,显然这个效果不理想,以上代码的边界逻辑存在问题,没有实现无缝循环播放。

无缝循环播放

实现无缝循环播放,我们需要向slider-content中的末尾添加上第1页。如下图

无缝循环播放示意图

html结构

<div class="container"> <!--视窗容器-->
    <div class="slider-content" style="left: -200px;"><!--图片容器-->
        <div class="slider-item">1</div>
        <div class="slider-item">2</div>
        <divclass="slider-item">3</div>
        <div class="slider-item">1</div>
    </div>
</div>
var content = document.querySelector('.slider-content');

class Slider  {
    constructor(ele){
        this.ele = ele;
        this.width = this.ele.parentNode.offsetWidth; // 获取宽度
        this.length = document.querySelectorAll('.slider-item').length;
        this.index = 0; // 当前页的索引值
        this.animate();        
    }

    move () {
        // 当前页的索引值等于最后一页,重置索引值为第一页
        if (this.index === this.length - 1) {
            this.index = 0;
        }
          
        this.index ++;
        this.ele.style.left = - this.index * this.width + 'px';     
    }


    animate() {
        setInterval(this.move.bind(this),2000)
    }
}

var box = new Slider(content);

以上代码新增了索引值index,用于记录当前窗口显示的是第几张图,当索引值不断递增,直到等于length-1时,表示显示的是最后一张。length-1就是边界点,此时将索引值重置为0,轮播图就实现了循环。

相反的往左翻页index--,递减的边界值为0,当index为0时,将index的值设置为length-1。

点击按钮翻页

点击按钮翻页,实际就是操作index值,向左翻页则index--,向右翻页则index++。需要注意的是,在操作翻页时,最好将定时器清除,以免造成效果混乱。

结合拖拽在react中封装轮播图

class Slider extends React.Component {
  constructor(props){
    super(props); 
    // props为组件传递的配置项
    this.speed = this.props.speed || 2000;  // 播放速度
    this.direction = this.props.direction || 'X'; // 翻页方向
    this.auto = this.props.auto || true; // 是否自动播放
    this.effect = this.props.effect ||'easeIn'; // 动效方式
    this.data = this.props.data || []; // 页面数据内容
    this.length = this.data.length + 1; // 页面数量
    this.width = this.props.width || 320; // 容器宽度
    this.height = this.props.height || 200; // 容器高度
    this.index = 0; // 当前页索引值
    this.timer = 0; // 定时器ID
    /**
     * 是否正在自动滚动
     */
    this.autoRun = false;

    // 根据配置的方向,设置翻页的值
    if(this.direction === 'X') { 
      this.dir = parseInt(this.width);
    }else {
      this.dir = parseInt(this.height);
    }

    if(this.auto) {
      this.autoRun = true;
      this.timer = setInterval(this.autoPlay, this.speed);    
    }
  }

  /**
   * 移动到第几页
   * @param {number} index 第几页
   * @param {string} direction 方向,从 'left, right, up, down' 中取一个作为值
   */
  setIndex = (direction) => {
    let to = 0;
    const ele = this.refs.sliderContent.refs.dragElement;

    /**
     * 点击左侧按钮,index --,边界点为0
     */
    if (direction === 'left') {
      // 初始位置就是极限位置的时候
      if (this.index === 0) {
        this.index = this.length - 1;
        ele.style.transform = `translate${this.direction}(${- this.index * this.dir}px)`;
      }
      to = - (this.index - 1) * this.dir;
      this.index --;
    }

    /**
     * 点击右侧按钮,到下一页,index ++,边界点为length-1
     */
    if (direction === 'right') {
      if (this.index === this.length - 1) {
        this.index = 0;
        ele.style.transform = `translate${this.direction}(${0}px)`;
      }
      to = - (this.index + 1) * this.dir;
      this.index ++;
    }

    // 此处结合了封装的运动框架
    animate(ele, this.effect, { 
      to,
      direction: this.direction
    }, () => {
      if (direction === 'right' && this.index === this.length - 1) {
        ele.style.transform = `translate${this.direction}(${0}px)`;
        this.index = 0;
      }
      if (direction === 'left' && this.index === 0) {
        ele.style.transform = `translate${this.direction}(${- this.index * this.dir}px)`;
        this.index = this.length - 1;
      }

      if (!this.autoRun) {
        this.timer = setInterval(() => this.setIndex(direction), this.speed);
        this.autoRun = true;
      }
    })
  }

  clickPrev = () => {
    this.autoRun = false;
    clearInterval(this.timer);
    this.setIndex('left');
  }

  clickNext = () => {
    this.autoRun = false;
    clearInterval(this.timer);
    this.setIndex('right');
  }

  autoPlay = () => {
    this.setIndex('right');
  }

  // 此处结合了封装的拖拽组件,获取滑动的差值,判断滑动方向
  touchEnd = (dis) => {
    let that = this;
    this.autoRun = false;
    clearInterval(this.timer);

    if(Math.abs(dis[this.direction]) > 60) {     
      dis[this.direction] > 0 ? this.clickNext() : this.clickPrev();      
    }

  }

  render() {
    const sliderItem = [...this.data, this.data[0]];
    return (
    <div className="container" ref="container" >
        <div className="prev" onClick={this.clickPrev}></div>
        <div className="next" onClick={this.clickNext}></div>
        <div ref="sliderContent" >
          <Drag className="sliderContent" onDragEnd={this.touchEnd}>           
            {sliderItem.map((item,i) =>
              <li className="sliderItem" key={i}>{item}</li>)
            }
          </Drag>
        </div>
        <Drag className="sliderContent" ref="sliderContent" onDragEnd={this.touchEnd}>
          {sliderItem.map((item,i) =>
            <li className="sliderItem" key={i}>{item}</li>)
          }
        </Drag>
      </div>
    )
  }
}

export default Slider;

以上轮播的封装,结合了上一篇的拖拽封装和另一个运动封装,完整的代码可以点击这里查看

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,092评论 4 62
  • 进入前端将近一年了,js还是很弱,突发奇想写一个轮播图,就找到了这个博主的材料,和大家分享。 轮播图的原理: 一系...
    FRRRR阅读 3,659评论 0 11
  • 分类问题概述 前面,我们已经讨论了关于数据预处理的一些细节。而在数据分析实战中,我们面对的一项重要工作,就是对数据...
    Clemente阅读 17,684评论 0 14
  • 对这样一部小说理应心怀敬意,我承认这一个月的阅读是一个充满着意外和惊喜的旅程,久违了这种读书的乐趣,以至于我在此刻...
    凌杰_owlman阅读 2,706评论 1 5
  • 飞翔的云儿阅读 187评论 0 0