【已开源】是时候替换传统轮播组件了,新式遮罩轮播组件,了解一下

93 阅读4分钟

背景

最近有一个页面改版的需求,在UI走查阶段,设计师说原来的轮播组件和新版页面UI整体风格不搭,所以要换掉。

这里就涉及到两种轮播组件,一种是传统的轮播组件,一种是设计师要的那种。

传统的轮播组件,大家都见过,原理也清楚,就是把要轮播的图片横向排成一个队列,把他们当成一个整体,每次轮换,其实是把这个队列整体往左平移X像素,这里的X通常就是一个图片的宽度。
这种效果可以参见vant组件库里的swipe组件

而我们设计师要的轮播效果是另外一种,因为我利用端午假期已经做好了一个雏形,所以大家可以直接看Demo

当然你也可以直接打开 腾讯视频APP 首页,顶部的轮播,就是我们设计师要的效果。

需求分析

新式轮播,涉及要两个知识点:

  • 图片层叠
  • 揭开效果

与传统轮播效果一个最明显的不同是,新的轮播效果需要把N张待轮播的图片在Z轴上重叠放置,每次揭开其中的一张,下一张是自然漏出来的。这里的实现方式也有多种,但最先想到的还是用zindex的方案。

第二个问题是如何实现揭开的效果。这里就要使用到css3的新属性mask。
mask是一系列css的简化属性。包括mask-image, mask-position等。
因为mask的系列属性还有一定的兼容性,所以一部分浏览器需要带上-webkit-前缀才能生效。

还有少数浏览器不支持mask属性,退化的情况是轮播必须有效,但是没有轮换的动效。

实现

有了以上的分析,就可以把效果做出来了。核心代码如下:

                                               // 监听currentIndex变化
                                               watch(currentIndex, () => {
                                                 if (currentIndex.value === 0) {
                                                     zIndexArr.value = [...getInitZindex()];
                                                       }
                                                         maskPosition.value = props.maskPositionFrom || 'left';
                                                           transition.value = 'none';
                                                           })
                                                            // 执行动画
                                                            const execAnimation = () => {
                                                              transition.value = `all ${props.transitionDuration || 1}s`;
                                                                maskPosition.value = props.maskPositionFrom || 'left';
                                                                  maskPosition.value = props.maskPositionTo || 'right';
                                                                    oldCurrentIndex.value = (currentIndex.value + 1) % (imgList.value.length - 1);
                                                                       setTimeout(() => {
                                                                           zIndexArr.value[currentIndex.value] = 1;
                                                                               currentIndex.value = (currentIndex.value + 1) % (imgList.value.length - 1);
                                                                                 }, 1000)
                                                                                 }
                                                                                  // 挂载时执行动画
                                                                                  onMounted(() => {
                                                                                    const firstDelay = duration - transitionDuration;
                                                                                      function animate() {
                                                                                          execAnimation();
                                                                                              setTimeout(animate, duration);
                                                                                                }
                                                                                                  setTimeout(animate, firstDelay);
                                                                                                  })
                                                                                                   </script>
                                                                                                    <template>
                                                                                                      <div class=&quot;fly-swipe-container&quot;>
                                                                                                          <div class=&quot;swipe-item&quot;
                                                                                                                   :class=&quot;{'swipe-item-mask': index === currentIndex}&quot;
                                                                                                                            v-for=&quot;(url, index) in imgList&quot;
                                                                                                                                     :key=&quot;index&quot;
                                                                                                                                              :style=&quot;{ zIndex: zIndexArr[index],
                                                                                                                                                       'transition': index === currentIndex ? transition : 'none',
                                                                                                                                                                'mask-image': index === currentIndex ? `url(${maskImageUrl})` : '',
                                                                                                                                                                         '-webkit-mask-image': index === currentIndex ? `url(${maskImageUrl})`: '',
                                                                                                                                                                                  'mask-position':  index === currentIndex ? maskPosition: '',
                                                                                                                                                                                           '-webkit-mask-position':  index === currentIndex ? maskPosition: '' }&quot;>
                                                                                                                                                                                                 
                                                                                                                                                                                                     </div>
                                                                                                                                                                                                         <div class=&quot;fly-indicator&quot;>
                                                                                                                                                                                                               <div class=&quot;fly-indicator-item&quot;
                                                                                                                                                                                                                          :class=&quot;{'fly-indicator-item-active': index === oldCurrentIndex}&quot;
                                                                                                                                                                                                                                     v-for=&quot;(_, index) in imgList.slice(0, imgList.length - 1)&quot;
                                                                                                                                                                                                                                                :key=&quot;index&quot;></div>
                                                                                                                                                                                                                                                    </div>
                                                                                                                                                                                                                                                      </div>
                                                                                                                                                                                                                                                      </template>
                                                                                                                                                                                                                                                       <style lang=&quot;less&quot; scoped>
                                                                                                                                                                                                                                                       .fly-swipe-container {
                                                                                                                                                                                                                                                         position: relative;
                                                                                                                                                                                                                                                           overflow: hidden;
                                                                                                                                                                                                                                                             width: 100%;
                                                                                                                                                                                                                                                               height: inherit;
                                                                                                                                                                                                                                                                  .swipe-item:first-child {
                                                                                                                                                                                                                                                                      position: relative;
                                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                                          .swipe-item {
                                                                                                                                                                                                                                                                              position: absolute;
                                                                                                                                                                                                                                                                                  width: 100%;
                                                                                                                                                                                                                                                                                      top: 0;
                                                                                                                                                                                                                                                                                          left: 0;
                                                                                                                                                                                                                                                                                               img {
                                                                                                                                                                                                                                                                                                     display: block;
                                                                                                                                                                                                                                                                                                           width: 100%;
                                                                                                                                                                                                                                                                                                                 object-fit: cover;
                                                                                                                                                                                                                                                                                                                     }
                                                                                                                                                                                                                                                                                                                       }
                                                                                                                                                                                                                                                                                                                          .swipe-item-mask {
                                                                                                                                                                                                                                                                                                                              mask-repeat: no-repeat;
                                                                                                                                                                                                                                                                                                                                  -webkit-mask-repeat: no-repeat;
                                                                                                                                                                                                                                                                                                                                      mask-size: cover;
                                                                                                                                                                                                                                                                                                                                          -webkit-mask-size: cover;
                                                                                                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                                                                                                               .fly-indicator {
                                                                                                                                                                                                                                                                                                                                                   display: flex;
                                                                                                                                                                                                                                                                                                                                                       justify-content: center;
                                                                                                                                                                                                                                                                                                                                                           align-items: center;
                                                                                                                                                                                                                                                                                                                                                               z-index: 666;
                                                                                                                                                                                                                                                                                                                                                                   position: relative;
                                                                                                                                                                                                                                                                                                                                                                       top: -20px;
                                                                                                                                                                                                                                                                                                                                                                           .fly-indicator-item {
                                                                                                                                                                                                                                                                                                                                                                                 margin: 0 5px;
                                                                                                                                                                                                                                                                                                                                                                                       width: 10px;
                                                                                                                                                                                                                                                                                                                                                                                             height: 10px;
                                                                                                                                                                                                                                                                                                                                                                                                   border-radius: 50%;
                                                                                                                                                                                                                                                                                                                                                                                                         background: gray;
                                                                                                                                                                                                                                                                                                                                                                                                             }
                                                                                                                                                                                                                                                                                                                                                                                                                 .fly-indicator-item-active {
                                                                                                                                                                                                                                                                                                                                                                                                                       background: #fff;
                                                                                                                                                                                                                                                                                                                                                                                                                           }
                                                                                                                                                                                                                                                                                                                                                                                                                             }
                                                                                                                                                                                                                                                                                                                                                                                                                             }
                                                                                                                                                                                                                                                                                                                                                                                                                             </style>" aria-label="复制" data-bs-original-title="复制">
                      <i class="far fa-copy"></i>
          </button>
</div>
      </div><pre class="javascript hljs language-javascript">&lt;script setup lang=<span class="hljs-string">"ts"</span>&gt;
       <span class="hljs-keyword">import</span> { ref, onMounted, watch } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
        <span class="hljs-comment">// 定义属性</span>
        <span class="hljs-keyword">const</span> props = <span class="hljs-title function_">defineProps</span>([
          <span class="hljs-string">'imgList'</span>,
            <span class="hljs-string">'duration'</span>, 
              <span class="hljs-string">'transitionDuration'</span>,
                <span class="hljs-string">'maskPositionFrom'</span>, 
                  <span class="hljs-string">'maskPositionTo'</span>,
                    <span class="hljs-string">'maskImageUrl'</span>
                    ]);
                     <span class="hljs-comment">// 定义响应式变量</span>
                     <span class="hljs-keyword">const</span> currentIndex = <span class="hljs-title function_">ref</span>(<span class="hljs-number">0</span>);
                     <span class="hljs-keyword">const</span> oldCurrentIndex = <span class="hljs-title function_">ref</span>(<span class="hljs-number">0</span>);
                     <span class="hljs-keyword">const</span> imgList = <span class="hljs-title function_">ref</span>([...props.<span class="hljs-property">imgList</span>, props.<span class="hljs-property">imgList</span>[<span class="hljs-number">0</span>]]);
                     <span class="hljs-keyword">const</span> <span class="hljs-title function_">getInitZindex</span> = (<span class="hljs-params"></span>) =&gt; {
                       <span class="hljs-keyword">const</span> arr = [<span class="hljs-number">1</span>];
                         <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = imgList.<span class="hljs-property">value</span>.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>; i &gt;= <span class="hljs-number">1</span>; i--) {
                             arr.<span class="hljs-title function_">unshift</span>(arr[<span class="hljs-number">0</span>] + <span class="hljs-number">1</span>);
                               }
                                 <span class="hljs-keyword">return</span> arr;
                                 }
                                 <span class="hljs-keyword">const</span> zIndexArr = <span class="hljs-title function_">ref</span>([...<span class="hljs-title function_">getInitZindex</span>()]);
                                 <span class="hljs-keyword">const</span> maskPosition = <span class="hljs-title function_">ref</span>(props.<span class="hljs-property">maskPositionFrom</span> || <span class="hljs-string">'left'</span>);
                                 <span class="hljs-keyword">const</span> transition = <span class="hljs-title function_">ref</span>(<span class="hljs-string">`all <span class="hljs-subst">${props.transitionDuration || <span class="hljs-number">1</span>}</span>s`</span>);
                                  <span class="hljs-comment">// 设置动画参数</span>
                                  <span class="hljs-keyword">const</span> transitionDuration = props.<span class="hljs-property">transitionDuration</span> || <span class="hljs-number">1000</span>;
                                  <span class="hljs-keyword">const</span> duration = props.<span class="hljs-property">duration</span> || <span class="hljs-number">3000</span>;
                                  
                                   <span class="hljs-comment">// 监听currentIndex变化</span>
                                   <span class="hljs-title function_">watch</span>(currentIndex, <span class="hljs-function">() =&gt;</span> {
                                     <span class="hljs-keyword">if</span> (currentIndex.<span class="hljs-property">value</span> === <span class="hljs-number">0</span>) {
                                         zIndexArr.<span class="hljs-property">value</span> = [...<span class="hljs-title function_">getInitZindex</span>()];
                                           }
                                             maskPosition.<span class="hljs-property">value</span> = props.<span class="hljs-property">maskPositionFrom</span> || <span class="hljs-string">'left'</span>;
                                               transition.<span class="hljs-property">value</span> = <span class="hljs-string">'none'</span>;
                                               })
                                                <span class="hljs-comment">// 执行动画</span>
                                                <span class="hljs-keyword">const</span> <span class="hljs-title function_">execAnimation</span> = (<span class="hljs-params"></span>) =&gt; {
                                                  transition.<span class="hljs-property">value</span> = <span class="hljs-string">`all <span class="hljs-subst">${props.transitionDuration || <span class="hljs-number">1</span>}</span>s`</span>;
                                                    maskPosition.<span class="hljs-property">value</span> = props.<span class="hljs-property">maskPositionFrom</span> || <span class="hljs-string">'left'</span>;
                                                      maskPosition.<span class="hljs-property">value</span> = props.<span class="hljs-property">maskPositionTo</span> || <span class="hljs-string">'right'</span>;
                                                        oldCurrentIndex.<span class="hljs-property">value</span> = (currentIndex.<span class="hljs-property">value</span> + <span class="hljs-number">1</span>) % (imgList.<span class="hljs-property">value</span>.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>);
                                                           <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
                                                               zIndexArr.<span class="hljs-property">value</span>[currentIndex.<span class="hljs-property">value</span>] = <span class="hljs-number">1</span>;
                                                                   currentIndex.<span class="hljs-property">value</span> = (currentIndex.<span class="hljs-property">value</span> + <span class="hljs-number">1</span>) % (imgList.<span class="hljs-property">value</span>.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>);
                                                                     }, <span class="hljs-number">1000</span>)
                                                                     }
                                                                      <span class="hljs-comment">// 挂载时执行动画</span>
                                                                      <span class="hljs-title function_">onMounted</span>(<span class="hljs-function">() =&gt;</span> {
                                                                        <span class="hljs-keyword">const</span> firstDelay = duration - transitionDuration;
                                                                          <span class="hljs-keyword">function</span> <span class="hljs-title function_">animate</span>(<span class="hljs-params"></span>) {
                                                                              <span class="hljs-title function_">execAnimation</span>();
                                                                                  <span class="hljs-built_in">setTimeout</span>(animate, duration);
                                                                                    }
                                                                                      <span class="hljs-built_in">setTimeout</span>(animate, firstDelay);
                                                                                      })
                                                                                       &lt;/script&gt;
                                                                                        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
                                                                                          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fly-swipe-container"</span>&gt;</span>
                                                                                              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"swipe-item"</span>
                                                                                                       <span class="hljs-attr">:class</span>=<span class="hljs-string">"{'swipe-item-mask': index === currentIndex}"</span>
                                                                                                                <span class="hljs-attr">v-for</span>=<span class="hljs-string">"(url, index) in imgList"</span>
                                                                                                                         <span class="hljs-attr">:key</span>=<span class="hljs-string">"index"</span>
                                                                                                                                  <span class="hljs-attr">:style</span>=<span class="hljs-string">"{ zIndex: zIndexArr[index],
                                                                                                                                           'transition': index === currentIndex ? transition : 'none',
                                                                                                                                                    'mask-image': index === currentIndex ? `url(${maskImageUrl})` : '',
                                                                                                                                                             '-webkit-mask-image': index === currentIndex ? `url(${maskImageUrl})`: '',
                                                                                                                                                                      'mask-position':  index === currentIndex ? maskPosition: '',
                                                                                                                                                                               '-webkit-mask-position':  index === currentIndex ? maskPosition: '' }"</span>&gt;</span>
                                                                                                                                                                                     <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">:src</span>=<span class="hljs-string">"url"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span>&gt;</span>
                                                                                                                                                                                         <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                                                                                                                                                                                             <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fly-indicator"</span>&gt;</span>
                                                                                                                                                                                                   <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"fly-indicator-item"</span>
                                                                                                                                                                                                              <span class="hljs-attr">:class</span>=<span class="hljs-string">"{'fly-indicator-item-active': index === oldCurrentIndex}"</span>
                                                                                                                                                                                                                         <span class="hljs-attr">v-for</span>=<span class="hljs-string">"(_, index) in imgList.slice(0, imgList.length - 1)"</span>
                                                                                                                                                                                                                                    <span class="hljs-attr">:key</span>=<span class="hljs-string">"index"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                                                                                                                                                                                                                                        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                                                                                                                                                                                                                                          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                                                                                                                                                                                                                                          <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
                                                                                                                                                                                                                                           <span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"less"</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="language-css">
                                                                                                                                                                                                                                           <span class="hljs-selector-class">.fly-swipe-container</span> {
                                                                                                                                                                                                                                             <span class="hljs-attribute">position</span>: relative;
                                                                                                                                                                                                                                               <span class="hljs-attribute">overflow</span>: hidden;
                                                                                                                                                                                                                                                 <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
                                                                                                                                                                                                                                                   <span class="hljs-attribute">height</span>: inherit;
                                                                                                                                                                                                                                                      <span class="hljs-selector-class">.swipe-item</span><span class="hljs-selector-pseudo">:first</span>-child {
                                                                                                                                                                                                                                                          <span class="hljs-attribute">position</span>: relative;
                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                              <span class="hljs-selector-class">.swipe-item</span> {
                                                                                                                                                                                                                                                                  <span class="hljs-attribute">position</span>: absolute;
                                                                                                                                                                                                                                                                      <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
                                                                                                                                                                                                                                                                          <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
                                                                                                                                                                                                                                                                              <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
                                                                                                                                                                                                                                                                                   <span class="hljs-selector-tag">img</span> {
                                                                                                                                                                                                                                                                                         <span class="hljs-attribute">display</span>: block;
                                                                                                                                                                                                                                                                                               <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
                                                                                                                                                                                                                                                                                                     <span class="hljs-attribute">object-fit</span>: cover;
                                                                                                                                                                                                                                                                                                         }
                                                                                                                                                                                                                                                                                                           }
                                                                                                                                                                                                                                                                                                              <span class="hljs-selector-class">.swipe-item-mask</span> {
                                                                                                                                                                                                                                                                                                                  <span class="hljs-attribute">mask-repeat</span>: no-repeat;
                                                                                                                                                                                                                                                                                                                      -webkit-<span class="hljs-attribute">mask-repeat</span>: no-repeat;
                                                                                                                                                                                                                                                                                                                          <span class="hljs-attribute">mask-size</span>: cover;
                                                                                                                                                                                                                                                                                                                              -webkit-<span class="hljs-attribute">mask-size</span>: cover;
                                                                                                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                                                                                                   <span class="hljs-selector-class">.fly-indicator</span> {
                                                                                                                                                                                                                                                                                                                                       <span class="hljs-attribute">display</span>: flex;
                                                                                                                                                                                                                                                                                                                                           <span class="hljs-attribute">justify-content</span>: center;
                                                                                                                                                                                                                                                                                                                                               <span class="hljs-attribute">align-items</span>: center;
                                                                                                                                                                                                                                                                                                                                                   <span class="hljs-attribute">z-index</span>: <span class="hljs-number">666</span>;
                                                                                                                                                                                                                                                                                                                                                       <span class="hljs-attribute">position</span>: relative;
                                                                                                                                                                                                                                                                                                                                                           <span class="hljs-attribute">top</span>: -<span class="hljs-number">20px</span>;
                                                                                                                                                                                                                                                                                                                                                               <span class="hljs-selector-class">.fly-indicator-item</span> {
                                                                                                                                                                                                                                                                                                                                                                     <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> <span class="hljs-number">5px</span>;
                                                                                                                                                                                                                                                                                                                                                                           <span class="hljs-attribute">width</span>: <span class="hljs-number">10px</span>;
                                                                                                                                                                                                                                                                                                                                                                                 <span class="hljs-attribute">height</span>: <span class="hljs-number">10px</span>;
                                                                                                                                                                                                                                                                                                                                                                                       <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">50%</span>;
                                                                                                                                                                                                                                                                                                                                                                                             <span class="hljs-attribute">background</span>: gray;
                                                                                                                                                                                                                                                                                                                                                                                                 }
                                                                                                                                                                                                                                                                                                                                                                                                     <span class="hljs-selector-class">.fly-indicator-item-active</span> {
                                                                                                                                                                                                                                                                                                                                                                                                           <span class="hljs-attribute">background</span>: <span class="hljs-number">#fff</span>;
                                                                                                                                                                                                                                                                                                                                                                                                               }
                                                                                                                                                                                                                                                                                                                                                                                                                 }
                                                                                                                                                                                                                                                                                                                                                                                                                 }
                                                                                                                                                                                                                                                                                                                                                                                                                 </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span></pre><p>这是一个使用 Vue 3 构建的图片轮播组件。在这个组件中,我们可以通过传入一组图片列表、切换动画的持续时间、过渡动画的持续时间、遮罩层的起始位置、遮罩层的结束位置以及遮罩层的图片 URL 来自定义轮播效果。</p><p>组件首先通过  <code>defineProps</code>  定义了一系列的属性,并使用  <code>ref</code>  创建了一些响应式变量,如  <code>currentIndex</code><code>oldCurrentIndex</code><code>imgList</code><code>zIndexArr</code>  等。</p><p><code>onMounted</code>  钩子函数中,我们设置了一个定时器,用于每隔一段时间执行一次轮播动画。<br> 在模板部分,我们使用了一个  <code>v-for</code>  指令来遍历图片列表,并根据当前图片的索引值为每个图片元素设置相应的样式。同时,我们还为每个图片元素添加了遮罩层,以实现轮播动画的效果。</p><p>在样式部分,我们定义了一些基本的样式,如轮播容器的大小、图片元素的位置等。此外,我们还为遮罩层设置了一些样式,包括遮罩图片的 URL、遮罩层的位置等。</p><p>总之,这是一个功能丰富的图片轮播组件,可以根据传入的参数自定义轮播效果。</p><h2 id="item-0-4">后续</h2><p>因为mask可以做的效果还有很多,后续该组件可以封装更多轮播效果,比如从多个方向的揭开效果,各种渐变方式揭开效果。欢迎使用和提建议。</p><p>仓库地址:<a href="https://link.segmentfault.com/?enc=EbBTA%2FgZkO%2Bmy72Zj6NJyw%3D%3D.LiygufFIUHovcnCmFAKk%2FcZwJI%2BzfJBGwPosUqO40Ybc1cNf72sNyX9f%2B1k2Y40Z" rel="nofollow" target="_blank">https://github.com/cunzaizhuyi/fly-comp</a></p>