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

用面向数据的思想实现3D旋转轮播图 #1

Open
liuyib opened this issue Jan 19, 2019 · 0 comments
Open

用面向数据的思想实现3D旋转轮播图 #1

liuyib opened this issue Jan 19, 2019 · 0 comments

Comments

@liuyib
Copy link
Owner

liuyib commented Jan 19, 2019

实现效果如下:

slider_show

当然开始之前,你需要先准备几张图片,这里建议 5 张。
因为这个效果的实现原理是以数据为基础的,我这里只有五个可以拿来即用的数据。
如果图片超出五张的话,你需要自己手动写一下需要的数据 : )

戳这里获取我使用的图片


页面基础布局和样式如下:

HTML:

查看内容
<div class="slider_wrapper" id="slider-wrapper">
  <div class="slider_imgs" id="slider-imgs">
    <ul>
      <li><img src="./imgs/1.png" alt="slider_img" /></li>
      <li><img src="./imgs/2.png" alt="slider_img" /></li>
      <li><img src="./imgs/3.png" alt="slider_img" /></li>
      <li><img src="./imgs/4.png" alt="slider_img" /></li>
      <li><img src="./imgs/5.png" alt="slider_img" /></li>
    </ul>

    <div class="slider_arrow" id="slider-arrow">
      <span id="slider-arrow-left">&lt;</span>
      <span id="slider-arrow-right">&gt;</span>
    </div>
  </div>
</div>

CSS:

查看内容
div,
span,
ul,
li,
img {
  margin: 0;
  padding: 0;
}
body {
  overflow: hidden;
  background: #666;
  padding: 50px;
}
li {
  list-style: none;
}

.slider_wrapper {
  width: 1200px;
  margin: 0 auto;
  margin-top: 60px;
}
.slider_imgs {
  position: relative;
  height: 430px;
  margin: 0 auto;
}
.slider_imgs li {
  position: absolute;
}
.slider_imgs img {
  width: 100%;
}

.slider_arrow {
  z-index: 999;
  position: relative;
  top: 50%;
  opacity: 0;
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  transition: opacity 0.3s ease;
}
.slider_wrapper:hover .slider_arrow {
  opacity: 0.5;
}
#slider-arrow-left,
#slider-arrow-right {
  z-index: 999;
  display: block;
  position: absolute;
  width: 50px;
  height: 50px;
  line-height: 40px;
  border-radius: 6px;
  text-align: center;
  font-size: 50px;
  font-weight: 600;
  background: #a9a9a9;
  color: #333;
  transform: translate(0, -50%);
  cursor: pointer;
}
#slider-arrow-right {
  right: 0;
}

样式和 DOM 结构不多 BB,主要的心思放在效果实现上。

这里我用了一个封装好的运动函数: startMove,代码如下:

/**
 * 获取某个属性的值
 * @param {Object} elem 当前元素
 * @param {String} attr 属性
 */
function getStyle(elem, attr) {
  if (window.getComputedStyle) {
    return window.getComputedStyle(elem, null)[attr];
  } else {
    return elem.currentStyle[attr];
  }
}

/**
 * 缓冲运动函数
 * @param {Object}   elem 当前元素
 * @param {Object}   json 要操作的属性 // 传透明度属性时,范围是0-100
 * @param {Function} fn   回调函数
 * @param {Number}   slow 动画的缓慢程度 // 数值越大,动画越迟缓。
 * @param {Number}   time 每次动画执行时间
 */
function startMove(elem, json, fn = null, slow = 10, time = 15) {
  clearInterval(elem.timer);

  elem.timer = setInterval(function() {
    var bStop = true;

    for (var attr in json) {
      var iCur = 0;
      var target = json[attr];

      if (attr === "opacity") {
        iCur = getStyle(elem, attr) * 100 || 1;
      } else {
        iCur = parseInt(getStyle(elem, attr)) || 0;
      }

      var iSpeed = (target - iCur) / slow;
      iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
      iCur += iSpeed;

      if (attr === "opacity") {
        elem.style.filter = "alpha(opacity=" + iCur + ")";
        elem.style[attr] = iCur / 100;
      } else if (attr === "zIndex") {
        elem.style.zIndex = target;
      } else {
        elem.style[attr] = iCur + "px";
      }

      iCur != target && (bStop = false);
    }

    if (bStop) {
      clearInterval(elem.timer);
      fn && fn();
    }
  }, time);
}

这个函数的作用是实现缓冲运动,可以同时操作多个 CSS 属性,实现一些比较复杂的动画。

举个栗子:

利用上面的 startMove 实现下面这个效果:

startmove_test1

代码如下:

查看内容
var oTest = document.getElementById("oTest");

startMove(
  oTest,
  {
    width: 100,
    height: 100,
    borderRadius: 100
  },
  function() {
    startMove(
      oTest,
      {
        width: 400,
        opacity: 60
      },
      function() {
        startMove(
          oTest,
          {
            left: 200,
            top: 100,
            width: 100
          },
          function() {
            startMove(
              oTest,
              {
                left: 150,
                top: 50,
                width: 200,
                height: 200
              },
              function() {
                startMove(oTest, {
                  left: 200,
                  top: 100,
                  width: 100,
                  height: 100
                });
              }
            );
          }
        );
      }
    );
  },
  60,
  20
);

当然,上面这个效果比较复杂,所以代码有点臃肿。这也是 startMove 函数的一个缺点。

回到正题,现在来看一下 3D 轮播的效果是怎么实现的:

首先准备五个数据(文章开始提到的,当然你可以自己随意调整),这五个数据分别对应着图片显示的大小,位置,透明度 和 层级关系。

var IMG_DATAS = [
  {
    width: 300,
    top: -20,
    left: 150,
    opacity: 20,
    zIndex: 2
  },
  {
    width: 500,
    top: 30,
    left: 50,
    opacity: 80,
    zIndex: 3
  },
  {
    width: 600,
    top: 100,
    left: 300,
    opacity: 100,
    zIndex: 4
  },
  {
    width: 500,
    top: 30,
    left: 650,
    opacity: 80,
    zIndex: 3
  },
  {
    width: 300,
    top: -20,
    left: 750,
    opacity: 20,
    zIndex: 2
  }
];

然后我们将这五个数据和五张图片分别传入运动函数 startMove

var oImgsWrapper = document.getElementById("slider-imgs");
var aImgs = oImgsWrapper.getElementsByTagName("li");

function sliderMove() {
  IMG_DATAS.forEach(function(item, index) {
    startMove(aImgs[index], item);
  });
}

sliderMove();

这样,初始加载页面时,图片就会自动去找自己的位置

slider_init

Cool!这就是面向数据的优点,我们只关心数据,通过数据来操控页面。数据的变化反应到页面上就实现了页面上的交互,动画,数据处理等等。

既然初始动画这么简单就完成了,那么切换的动画呢?!你可以先思考一下切换动画的实现。


这里揭晓一下答案:

既然是面向数据,我们就只关心数据,剩下的动画部分交给 startMove 处理就好了。

图片左右切换,实际也就是数组里五个数据的切换。我们只需模拟队列,改变数据的顺序即可:当图片左移时,将最后一个数据从数组中移出,然后将这个数据添加到数组开头,最后把处理后的数组传给 startMove 函数。这样,数据的流动反应到页面上就是图片的移动,很酷,不是吗?

图片右移和左移同理。

核心代码如下:

// 图片左移
IMG_DATAS.unshift(IMG_DATAS.pop());

// 图片右移
IMG_DATAS.push(IMG_DATAS.shift());

这里要特别注意一下:数据在数组中向左移动(也就是将数据从数组开头移出,然后添加到数组末尾),图片实际是向右移动的。这点不难理解,就好比水车,水往左流,而水车是向右旋转。

每次移动完以后,将处理后的数组传给 startMove:

function sliderMove() {
  IMG_DATAS.forEach(function(item, index) {
    startMove(aImgs[index], item);
  });
}

sliderMove();

完整代码如下:

查看内容
window.onload = function() {
  var IMG_DATAS = [
    {
      width: 300,
      top: -20,
      left: 150,
      opacity: 20,
      zIndex: 2
    },
    {
      width: 500,
      top: 30,
      left: 50,
      opacity: 80,
      zIndex: 3
    },
    {
      width: 600,
      top: 100,
      left: 300,
      opacity: 100,
      zIndex: 4
    },
    {
      width: 500,
      top: 30,
      left: 650,
      opacity: 80,
      zIndex: 3
    },
    {
      width: 300,
      top: -20,
      left: 750,
      opacity: 20,
      zIndex: 2
    }
  ];

  var oBtnLeft = document.getElementById("slider-arrow-left");
  var oBtnRight = document.getElementById("slider-arrow-right");
  var oImgsWrapper = document.getElementById("slider-imgs");
  var aImgs = oImgsWrapper.getElementsByTagName("li");

  // 图片轮播
  function sliderMove() {
    IMG_DATAS.forEach(function(item, index) {
      startMove(aImgs[index], item);
    });
  }

  // 页面初始加载,执行一次动画
  sliderMove();

  // 点击左按钮
  oBtnLeft.onclick = function() {
    IMG_DATAS.push(IMG_DATAS.shift());
    sliderMove();
  };

  // 点击右按钮
  oBtnRight.onclick = function() {
    IMG_DATAS.unshift(IMG_DATAS.pop());
    sliderMove();
  };
};

到此,基本的功能已经实现。


那么再思考一下,如何实现:点击图片将其显示在中间?

既然是面向数据,那么动画的实现原理都是类似的,只要合适的操纵数据即可。如果理解了上面左、右轮播的原理,那么这个效果的实现应该没太大问题,这里讲下我的思路。

  • 在图片的数据中,添加 index 属性,表示图片的索引
  • 计算出中间那张图片应该具有的 index 值,这里用变量 middle 表示。比如:有五张图片,那么 middle 应该为 2
  • 获取被点击的图片的 index 属性值,将其与 middle 比较,差值的绝对值就是对数据进行模拟队列操作的次数。
  • 然后对数组中的数据进行模拟队列操作即可。

改进后的完整代码如下:

查看内容
window.onload = function() {
  var IMG_DATAS = [
    {
      index: 0,
      width: 300,
      top: -20,
      left: 150,
      opacity: 20,
      zIndex: 2
    },
    {
      index: 1,
      width: 500,
      top: 30,
      left: 50,
      opacity: 80,
      zIndex: 3
    },
    {
      index: 2,
      width: 600,
      top: 100,
      left: 300,
      opacity: 100,
      zIndex: 4
    },
    {
      index: 3,
      width: 500,
      top: 30,
      left: 650,
      opacity: 80,
      zIndex: 3
    },
    {
      index: 4,
      width: 300,
      top: -20,
      left: 750,
      opacity: 20,
      zIndex: 2
    }
  ];

  var oBtnLeft = document.getElementById("slider-arrow-left");
  var oBtnRight = document.getElementById("slider-arrow-right");
  var oImgsWrapper = document.getElementById("slider-imgs");
  var aImgs = oImgsWrapper.getElementsByTagName("li");
  // 计算中间的图片的索引
  var nMiddleImgIndex = Math.floor(aImgs.length / 2);

  for (let i = 0; i < aImgs.length; i++) {
    aImgs[i].onclick = function() {
      // 计算数据需要模拟队列移动的次数
      var nMoveLen = IMG_DATAS[i].index - nMiddleImgIndex;

      if (!nMoveLen) return; // 图片已经在中间
      if (nMoveLen < 0) {
        // 点击左边的图片,图片右移
        for (let j = 0; j < Math.abs(nMoveLen); j++) {
          IMG_DATAS.push(IMG_DATAS.shift());
        }
      } else {
        // 点击右边的图片,图片左移
        for (let j = 0; j < nMoveLen; j++) {
          IMG_DATAS.unshift(IMG_DATAS.pop());
        }
      }

      sliderMove();
    };
  }

  // 图片轮播
  function sliderMove() {
    IMG_DATAS.forEach(function(item, index) {
      startMove(aImgs[index], item);
    });
  }

  // 页面初始加载,执行一次动画
  sliderMove();

  // 点击左按钮
  oBtnLeft.onclick = function() {
    IMG_DATAS.push(IMG_DATAS.shift());
    sliderMove();
  };

  // 点击右按钮
  oBtnRight.onclick = function() {
    IMG_DATAS.unshift(IMG_DATAS.pop());
    sliderMove();
  };
};

Demo 体验地址:https://liuyib.github.io/blog/demo/blog/slider-3D/

以上 🚀

@liuyib liuyib changed the title 用面向数据的思想实现3D旋转轮播图 💞 用面向数据的思想实现3D旋转轮播图 Jan 19, 2019
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

1 participant