Procházet zdrojové kódy

Carousel: add direction attribute and support vertical direction (#15122)

hetech před 6 roky
rodič
revize
2a65c9df7e

+ 9 - 10
examples/demo-styles/carousel.scss

@@ -3,7 +3,7 @@
   text-align: center;
   border-right: solid 1px #eff2f6;
   display: inline-block;
-  width: 50%;
+  width: 49%;
   box-sizing: border-box;
   &:last-child {
     border-right: none;
@@ -27,6 +27,14 @@
     font-size: 18px;
     line-height: 300px;
     margin: 0;
+    &.small {
+      font-size: 14px;
+      line-height: 150px;
+    }
+    &.medium {
+      font-size: 14px;
+      line-height: 200px;
+    }
   }
   &:nth-child(2n) {
     background-color: #99a9bf;
@@ -36,12 +44,3 @@
   }
 }
 
-.demo-carousel .small h3 {
-  font-size: 14px;
-  line-height: 150px;
-}
-
-.demo-carousel .medium h3 {
-  font-size: 14px;
-  line-height: 200px;
-}

+ 36 - 3
examples/docs/en-US/carousel.md

@@ -11,7 +11,7 @@ Loop a series of images or texts in a limited space
     <span class="demonstration">Switch when indicator is hovered (default)</span>
     <el-carousel height="150px">
       <el-carousel-item v-for="item in 4" :key="item">
-        <h3>{{ item }}</h3>
+        <h3 class="small">{{ item }}</h3>
       </el-carousel-item>
     </el-carousel>
   </div>
@@ -19,7 +19,7 @@ Loop a series of images or texts in a limited space
     <span class="demonstration">Switch when indicator is clicked</span>
     <el-carousel trigger="click" height="150px">
       <el-carousel-item v-for="item in 4" :key="item">
-        <h3>{{ item }}</h3>
+        <h3 class="small">{{ item }}</h3>
       </el-carousel-item>
     </el-carousel>
   </div>
@@ -122,7 +122,7 @@ When a page is wide enough but has limited height, you can activate card mode fo
 <template>
   <el-carousel :interval="4000" type="card" height="200px">
     <el-carousel-item v-for="item in 6" :key="item">
-      <h3>{{ item }}</h3>
+      <h3 class="medium">{{ item }}</h3>
     </el-carousel-item>
   </el-carousel>
 </template>
@@ -147,6 +147,38 @@ When a page is wide enough but has limited height, you can activate card mode fo
 ```
 :::
 
+By default, `direction` is `horizontal`. Let carousel be displayed in the vertical direction by setting `direction` to `vertical`.
+
+:::demo
+```html
+<template>
+  <el-carousel height="200px" direction="vertical" :autoplay="false">
+    <el-carousel-item v-for="item in 4" :key="item">
+      <h3 class="medium">{{ item }}</h3>
+    </el-carousel-item>
+  </el-carousel>
+</template>
+
+<style>
+  .el-carousel__item h3 {
+    color: #475669;
+    font-size: 14px;
+    opacity: 0.75;
+    line-height: 200px;
+    margin: 0;
+  }
+  
+  .el-carousel__item:nth-child(2n) {
+    background-color: #99a9bf;
+  }
+  
+  .el-carousel__item:nth-child(2n+1) {
+    background-color: #d3dce6;
+  }
+</style>
+```
+:::
+
 ### Carousel Attributes
 | Attribute      | Description          | Type      | Accepted Values       | Default  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
@@ -159,6 +191,7 @@ When a page is wide enough but has limited height, you can activate card mode fo
 | arrow | when arrows are shown | string | always/hover/never | hover |
 | type | type of the Carousel | string | card | — |
 | loop | display the items in loop | boolean | - | true |
+| direction | display direction | string | horizontal/vertical | horizontal |
 
 ### Carousel Events
 | Event Name | Description | Parameters |

+ 37 - 4
examples/docs/es/carousel.md

@@ -12,7 +12,7 @@ Presenta una serie de imágenes o textos en un espacio limitado
     <span class="demonstration">Switch when indicator is hovered (default)</span>
     <el-carousel height="150px">
       <el-carousel-item v-for="item in 4" :key="item">
-        <h3>{{ item }}</h3>
+        <h3 class="small">{{ item }}</h3>
       </el-carousel-item>
     </el-carousel>
   </div>
@@ -20,7 +20,7 @@ Presenta una serie de imágenes o textos en un espacio limitado
     <span class="demonstration">Switch when indicator is clicked</span>
     <el-carousel trigger="click" height="150px">
       <el-carousel-item v-for="item in 4" :key="item">
-        <h3>{{ item }}</h3>
+        <h3 class="small">{{ item }}</h3>
       </el-carousel-item>
     </el-carousel>
   </div>
@@ -126,7 +126,7 @@ Cuando una página es suficientemente ancha pero tiene una altura limitada, pued
 <template>
   <el-carousel :interval="4000" type="card" height="200px">
     <el-carousel-item v-for="item in 6" :key="item">
-      <h3>{{ item }}</h3>
+      <h3 class="medium">{{ item }}</h3>
     </el-carousel-item>
   </el-carousel>
 </template>
@@ -151,6 +151,38 @@ Cuando una página es suficientemente ancha pero tiene una altura limitada, pued
 ```
 :::
 
+By default, `direction` is `horizontal`. Let carousel be displayed in the vertical direction by setting `direction` to `vertical`.
+
+:::demo
+```html
+<template>
+  <el-carousel height="200px" direction="vertical" :autoplay="false">
+    <el-carousel-item v-for="item in 4" :key="item">
+      <h3 class="medium">{{ item }}</h3>
+    </el-carousel-item>
+  </el-carousel>
+</template>
+
+<style>
+  .el-carousel__item h3 {
+    color: #475669;
+    font-size: 14px;
+    opacity: 0.75;
+    line-height: 200px;
+    margin: 0;
+  }
+  
+  .el-carousel__item:nth-child(2n) {
+    background-color: #99a9bf;
+  }
+  
+  .el-carousel__item:nth-child(2n+1) {
+    background-color: #d3dce6;
+  }
+</style>
+```
+:::
+
 ### Atributos de Carousel
 | Atributo           | Descripcion                              | Tipo    | Valores aceptados  | Por defecto |
 | ------------------ | ---------------------------------------- | ------- | ------------------ | ----------- |
@@ -162,7 +194,8 @@ Cuando una página es suficientemente ancha pero tiene una altura limitada, pued
 | indicator-position | Posición del indicador de paginación     | string  | outside/none       | —           |
 | arrow              | Cuando se muestran las flechas           | string  | always/hover/never | hover       |
 | type               | Tipo de carrusel                         | string  | card               | —           |
-| loop               | Si se muestra cíclicamente                  | boolean | —                  | true        |
+| loop               | Si se muestra cíclicamente               | boolean | —                  | true        |
+| direction          | display direction                        | string  | horizontal/vertical| horizontal  |
 
 ### Eventos de Carousel
 | Nombre evento | Descripción                              | Parametros                               |

+ 36 - 3
examples/docs/fr-FR/carousel.md

@@ -11,7 +11,7 @@ Affiche en boucle une série d'images ou de textes dans un espace limité.
     <span class="demonstration">Défile quand la souris passe sur l'indicateur (défaut)</span>
     <el-carousel height="150px">
       <el-carousel-item v-for="item in 4" :key="item">
-        <h3>{{ item }}</h3>
+        <h3 class="small">{{ item }}</h3>
       </el-carousel-item>
     </el-carousel>
   </div>
@@ -19,7 +19,7 @@ Affiche en boucle une série d'images ou de textes dans un espace limité.
     <span class="demonstration">Défile quand on clique sur l'indicateur</span>
     <el-carousel trigger="click" height="150px">
       <el-carousel-item v-for="item in 4" :key="item">
-        <h3>{{ item }}</h3>
+        <h3 class="small">{{ item }}</h3>
       </el-carousel-item>
     </el-carousel>
   </div>
@@ -122,7 +122,7 @@ Quand la page est suffisement large mais avec une hauteur limitée, vous pouvez
 <template>
   <el-carousel :interval="4000" type="card" height="200px">
     <el-carousel-item v-for="item in 6" :key="item">
-      <h3>{{ item }}</h3>
+      <h3 class="medium">{{ item }}</h3>
     </el-carousel-item>
   </el-carousel>
 </template>
@@ -147,6 +147,38 @@ Quand la page est suffisement large mais avec une hauteur limitée, vous pouvez
 ```
 :::
 
+By default, `direction` is `horizontal`. Let carousel be displayed in the vertical direction by setting `direction` to `vertical`.
+
+:::demo
+```html
+<template>
+  <el-carousel height="200px" direction="vertical" :autoplay="false">
+    <el-carousel-item v-for="item in 4" :key="item">
+      <h3 class="medium">{{ item }}</h3>
+    </el-carousel-item>
+  </el-carousel>
+</template>
+
+<style>
+  .el-carousel__item h3 {
+    color: #475669;
+    font-size: 14px;
+    opacity: 0.75;
+    line-height: 200px;
+    margin: 0;
+  }
+  
+  .el-carousel__item:nth-child(2n) {
+    background-color: #99a9bf;
+  }
+  
+  .el-carousel__item:nth-child(2n+1) {
+    background-color: #d3dce6;
+  }
+</style>
+```
+:::
+
 ### Attributs du Carousel
 | Attribut      | Description          | Type      | Valeurs acceptées       | Défaut  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
@@ -159,6 +191,7 @@ Quand la page est suffisement large mais avec une hauteur limitée, vous pouvez
 | arrow | Détermine quand les flèches sont affichés. | string | always/hover/never | hover |
 | type | Type du carousel. | string | card | — |
 | loop | Affiche les éléments en boucle. | boolean | - | true |
+| direction | display direction | string | horizontal/vertical | horizontal |
 
 ### Évènements du Carousel
 | Nom | Description | Paramètres |

+ 36 - 3
examples/docs/zh-CN/carousel.md

@@ -13,7 +13,7 @@
     <span class="demonstration">默认 Hover 指示器触发</span>
     <el-carousel height="150px">
       <el-carousel-item v-for="item in 4" :key="item">
-        <h3>{{ item }}</h3>
+        <h3 class="small">{{ item }}</h3>
       </el-carousel-item>
     </el-carousel>
   </div>
@@ -21,7 +21,7 @@
     <span class="demonstration">Click 指示器触发</span>
     <el-carousel trigger="click" height="150px">
       <el-carousel-item v-for="item in 4" :key="item">
-        <h3>{{ item }}</h3>
+        <h3 class="small">{{ item }}</h3>
       </el-carousel-item>
     </el-carousel>
   </div>
@@ -122,7 +122,39 @@
 <template>
   <el-carousel :interval="4000" type="card" height="200px">
     <el-carousel-item v-for="item in 6" :key="item">
-      <h3>{{ item }}</h3>
+      <h3 class="medium">{{ item }}</h3>
+    </el-carousel-item>
+  </el-carousel>
+</template>
+
+<style>
+  .el-carousel__item h3 {
+    color: #475669;
+    font-size: 14px;
+    opacity: 0.75;
+    line-height: 200px;
+    margin: 0;
+  }
+  
+  .el-carousel__item:nth-child(2n) {
+    background-color: #99a9bf;
+  }
+  
+  .el-carousel__item:nth-child(2n+1) {
+    background-color: #d3dce6;
+  }
+</style>
+```
+:::
+
+### 方向
+默认情况下,`direction` 为 `horizontal`。通过设置 `direction` 为 `vertical` 来让走马灯在垂直方向上显示。
+:::demo
+```html
+<template>
+  <el-carousel height="200px" direction="vertical" :autoplay="false">
+    <el-carousel-item v-for="item in 3" :key="item">
+      <h3 class="medium">{{ item }}</h3>
     </el-carousel-item>
   </el-carousel>
 </template>
@@ -159,6 +191,7 @@
 | arrow | 切换箭头的显示时机 | string | always/hover/never | hover |
 | type | 走马灯的类型 | string | card | — |
 | loop | 是否循环显示 | boolean | - | true |
+| direction | 走马灯展示的方向 | string | horizontal/vertical | horizontal |
 
 ### Carousel Events
 | 事件名称 | 说明 | 回调参数 |

+ 34 - 11
packages/carousel/src/item.vue

@@ -10,11 +10,7 @@
       'is-animating': animating
     }"
     @click="handleItemClick"
-    :style="{
-      msTransform: `translateX(${ translate }px) scale(${ scale })`,
-      webkitTransform: `translateX(${ translate }px) scale(${ scale })`,
-      transform: `translateX(${ translate }px) scale(${ scale })`
-    }">
+    :style="itemStyle">
     <div
       v-if="$parent.type === 'card'"
       v-show="!active"
@@ -25,6 +21,7 @@
 </template>
 
 <script>
+  import { autoprefixer } from 'element-ui/src/utils/util';
   const CARD_SCALE = 0.83;
   export default {
     name: 'ElCarouselItem',
@@ -63,7 +60,8 @@
         return index;
       },
 
-      calculateTranslate(index, activeIndex, parentWidth) {
+      calcCardTranslate(index, activeIndex) {
+        const parentWidth = this.$parent.$el.offsetWidth;
         if (this.inStage) {
           return parentWidth * ((2 - CARD_SCALE) * (index - activeIndex) + 1) / 4;
         } else if (index < activeIndex) {
@@ -73,23 +71,33 @@
         }
       },
 
+      calcTranslate(index, activeIndex, isVertical) {
+        const distance = this.$parent.$el[isVertical ? 'offsetHeight' : 'offsetWidth'];
+        return distance * (index - activeIndex);
+      },
+
       translateItem(index, activeIndex, oldIndex) {
-        const parentWidth = this.$parent.$el.offsetWidth;
+        const parentType = this.$parent.type;
+        const parentDirection = this.parentDirection;
         const length = this.$parent.items.length;
-        if (this.$parent.type !== 'card' && oldIndex !== undefined) {
+        if (parentType !== 'card' && oldIndex !== undefined) {
           this.animating = index === activeIndex || index === oldIndex;
         }
         if (index !== activeIndex && length > 2 && this.$parent.loop) {
           index = this.processIndex(index, activeIndex, length);
         }
-        if (this.$parent.type === 'card') {
+        if (parentType === 'card') {
+          if (parentDirection === 'vertical') {
+            console.warn('[Element Warn][Carousel]vertical directionis not supported in card mode');
+          }
           this.inStage = Math.round(Math.abs(index - activeIndex)) <= 1;
           this.active = index === activeIndex;
-          this.translate = this.calculateTranslate(index, activeIndex, parentWidth);
+          this.translate = this.calcCardTranslate(index, activeIndex);
           this.scale = this.active ? 1 : CARD_SCALE;
         } else {
           this.active = index === activeIndex;
-          this.translate = parentWidth * (index - activeIndex);
+          const isVertical = parentDirection === 'vertical';
+          this.translate = this.calcTranslate(index, activeIndex, isVertical);
         }
         this.ready = true;
       },
@@ -103,6 +111,21 @@
       }
     },
 
+    computed: {
+      parentDirection() {
+        return this.$parent.direction;
+      },
+
+      itemStyle() {
+        const translateType = this.parentDirection === 'vertical' ? 'translateY' : 'translateX';
+        const value = `${translateType}(${ this.translate }px) scale(${ this.scale })`;
+        const style = {
+          transform: value
+        };
+        return autoprefixer(style);
+      }
+    },
+
     created() {
       this.$parent && this.$parent.updateItems();
     },

+ 48 - 12
packages/carousel/src/main.vue

@@ -1,16 +1,16 @@
 <template>
   <div
-    class="el-carousel"
-    :class="{ 'el-carousel--card': type === 'card' }"
+    :class="carouselClasses"
     @mouseenter.stop="handleMouseEnter"
     @mouseleave.stop="handleMouseLeave">
     <div
       class="el-carousel__container"
       :style="{ height: height }">
-      <transition name="carousel-arrow-left">
+      <transition
+        v-if="arrowDisplay"
+        name="carousel-arrow-left">
         <button
           type="button"
-          v-if="arrow !== 'never'"
           v-show="(arrow === 'always' || hover) && (loop || activeIndex > 0)"
           @mouseenter="handleButtonEnter('left')"
           @mouseleave="handleButtonLeave"
@@ -19,10 +19,11 @@
           <i class="el-icon-arrow-left"></i>
         </button>
       </transition>
-      <transition name="carousel-arrow-right">
+      <transition
+        v-if="arrowDisplay"
+        name="carousel-arrow-right">
         <button
           type="button"
-          v-if="arrow !== 'never'"
           v-show="(arrow === 'always' || hover) && (loop || activeIndex < items.length - 1)"
           @mouseenter="handleButtonEnter('right')"
           @mouseleave="handleButtonLeave"
@@ -34,16 +35,20 @@
       <slot></slot>
     </div>
     <ul
-      class="el-carousel__indicators"
       v-if="indicatorPosition !== 'none'"
-      :class="{ 'el-carousel__indicators--labels': hasLabel, 'el-carousel__indicators--outside': indicatorPosition === 'outside' || type === 'card' }">
+      :class="indicatorsClasses">
       <li
         v-for="(item, index) in items"
-        class="el-carousel__indicator"
-        :class="{ 'is-active': index === activeIndex }"
+        :key="index"
+        :class="[
+          'el-carousel__indicator',
+          'el-carousel__indicator--' + direction,
+          { 'is-active': index === activeIndex }]"
         @mouseenter="throttledIndicatorHover(index)"
         @click.stop="handleIndicatorClick(index)">
-        <button class="el-carousel__button"><span v-if="hasLabel">{{ item.label }}</span></button>
+        <button class="el-carousel__button">
+          <span v-if="hasLabel">{{ item.label }}</span>
+        </button>
       </li>
     </ul>
   </div>
@@ -87,6 +92,13 @@ export default {
     loop: {
       type: Boolean,
       default: true
+    },
+    direction: {
+      type: String,
+      default: 'horizontal',
+      validator(val) {
+        return ['horizontal', 'vertical'].indexOf(val) !== -1;
+      }
     }
   },
 
@@ -101,8 +113,31 @@ export default {
   },
 
   computed: {
+    arrowDisplay() {
+      return this.arrow !== 'never' && this.direction !== 'vertical';
+    },
+
     hasLabel() {
       return this.items.some(item => item.label.toString().length > 0);
+    },
+
+    carouselClasses() {
+      const classes = ['el-carousel', 'el-carousel--' + this.direction];
+      if (this.type === 'card') {
+        classes.push('el-carousel--card');
+      }
+      return classes;
+    },
+
+    indicatorsClasses() {
+      const classes = ['el-carousel__indicators', 'el-carousel__indicators--' + this.direction];
+      if (this.hasLabel) {
+        classes.push('el-carousel__indicators--labels');
+      }
+      if (this.indicatorPosition === 'outside' || this.type === 'card') {
+        classes.push('el-carousel__indicators--outside');
+      }
+      return classes;
     }
   },
 
@@ -149,6 +184,7 @@ export default {
     },
 
     handleButtonEnter(arrow) {
+      if (this.direction === 'vertical') return;
       this.items.forEach((item, index) => {
         if (arrow === this.itemInStage(item, index)) {
           item.hover = true;
@@ -157,6 +193,7 @@ export default {
     },
 
     handleButtonLeave() {
+      if (this.direction === 'vertical') return;
       this.items.forEach(item => {
         item.hover = false;
       });
@@ -201,7 +238,6 @@ export default {
       }
       index = Number(index);
       if (isNaN(index) || index !== Math.floor(index)) {
-        process.env.NODE_ENV !== 'production' &&
         console.warn('[Element Warn][Carousel]index must be an integer.');
         return;
       }

+ 33 - 6
packages/theme-chalk/src/carousel.scss

@@ -2,9 +2,16 @@
 @import "common/var";
 
 @include b(carousel) {
-  overflow-x: hidden;
   position: relative;
 
+  @include m(horizontal) {
+    overflow-x: hidden;
+  }
+
+  @include m(vertical) {
+    overflow-y: hidden;
+  }
+
   @include e(container) {
     position: relative;
     height: 300px;
@@ -49,13 +56,22 @@
   @include e(indicators) {
     position: absolute;
     list-style: none;
-    bottom: 0;
-    left: 50%;
-    transform: translateX(-50%);
     margin: 0;
     padding: 0;
     z-index: #{$--index-normal + 1};
 
+    @include m(horizontal) {
+      bottom: 0;
+      left: 50%;
+      transform: translateX(-50%);
+    }
+
+    @include m(vertical) {
+      right: 0;
+      top: 50%;
+      transform: translateY(-50%);
+    }
+
     @include m(outside) {
       bottom: #{$--carousel-indicator-height + $--carousel-indicator-padding-vertical * 2};
       text-align: center;
@@ -90,15 +106,26 @@
   }
 
   @include e(indicator) {
-    display: inline-block;
     background-color: transparent;
-    padding: $--carousel-indicator-padding-vertical $--carousel-indicator-padding-horizontal;
     cursor: pointer;
 
     &:hover button {
       opacity: 0.72;
     }
 
+    @include m(horizontal) {
+      display: inline-block;
+      padding: $--carousel-indicator-padding-vertical $--carousel-indicator-padding-horizontal;
+    }
+
+    @include m(vertical) {
+      padding: $--carousel-indicator-padding-horizontal $--carousel-indicator-padding-vertical;
+      .el-carousel__button {
+        width: $--carousel-indicator-height;
+        height: #{$--carousel-indicator-width / 2};
+      }
+    }
+
     @include when(active) {
       button {
         opacity: 1;

+ 15 - 0
src/utils/util.js

@@ -120,3 +120,18 @@ export const isIE = function() {
 export const isEdge = function() {
   return !Vue.prototype.$isServer && navigator.userAgent.indexOf('Edge') > -1;
 };
+
+export const autoprefixer = function(style) {
+  if (typeof style !== 'object') return style;
+  const rules = ['transform', 'transition', 'animation'];
+  const prefixes = ['ms-', 'webkit-'];
+  rules.forEach(rule => {
+    const value = style[rule];
+    if (rule && value) {
+      prefixes.forEach(prefix => {
+        style[prefix + rule] = value;
+      });
+    }
+  });
+  return style;
+};

+ 18 - 1
test/unit/specs/carousel.spec.js

@@ -10,12 +10,13 @@ describe('Carousel', () => {
     vm = createVue({
       template: `
         <div>
-          <el-carousel>
+          <el-carousel ref="carousel">
             <el-carousel-item v-for="item in 3" :key="item"></el-carousel-item>
           </el-carousel>
         </div>
       `
     });
+    expect(vm.$refs.carousel.direction).to.be.equal('horizontal');
     expect(vm.$el.querySelectorAll('.el-carousel__item').length).to.equal(3);
   });
 
@@ -256,5 +257,21 @@ describe('Carousel', () => {
       }, 10);
     }, 10);
   });
+
+  it('vertical direction', () => {
+    vm = createVue({
+      template: `
+        <div>
+          <el-carousel ref="carousel" :autoplay="false" direction="vertical" height="100px">
+            <el-carousel-item v-for="item in 3" :key="item"></el-carousel-item>
+          </el-carousel>
+        </div>
+      `
+    });
+    const items = vm.$el.querySelectorAll('.el-carousel__item');
+
+    expect(vm.$refs.carousel.direction).to.be.equal('vertical');
+    expect(items[0].style.transform.indexOf('translateY') !== -1).to.be.true;
+  });
 });
 

+ 4 - 0
types/carousel.d.ts

@@ -4,6 +4,7 @@ export type CarouselIndicatorTrigger = 'hover' | 'click'
 export type CarouselIndicatorPosition = 'outside' | 'none'
 export type CarouselArrowVisibility = 'always' | 'hover' | 'never'
 export type CarouselType = 'card'
+export type CarouselDirection = 'horizontal' | 'vertical'
 
 /** Loop a series of images or texts in a limited space */
 export declare class ElCarousel extends ElementUIComponent {
@@ -31,6 +32,9 @@ export declare class ElCarousel extends ElementUIComponent {
   /** Type of the Carousel */
   type: CarouselType
 
+  /** Display direction */
+  direction: CarouselDirection
+
   /**
    * Manually switch slide by index
    *