Implementation of special-shaped scrolling with swiper

Time:2021-10-17

essential information

Official introduction

Swiper is the free and most modern mobile touch slider with hardware accelerated transitions and amazing native behavior. It is intended to be used in mobile websites, mobile web apps, and mobile native/hybrid apps.

file

Usage scene – transition effect

  • Rotation chart

practice

target

Implement a special-shaped rotation chart. Support gesture left and right sliding to switch the center diagram.

Implementation of special-shaped scrolling with swiper

Principle of realizing special-shaped rolling

Center page (class:swiper-slide-activescale:1, other pages (class:swiper-slide) scale(0<x<1)To achieve the effect of the center page in the user’s visual center.scaleThe blank area is eliminated by the configuration item of the swiper (adjusting each swiper) + CSS style (overall element fine tuning).

Code reference (part)

index.vue:

    <div class="gallery-wrapper">
      <!-- Slider main container -->
      <div class="swiper-container">
          <!-- Additional required wrapper -->
          <div class="swiper-wrapper">
              <!-- Slides -->
              <div 
                class="swiper-slide" 
                v-for="(item,index) in images"
                :key="`${item.src}-index`"
              > 
                <img 
                  v-lazy="item.src" 
                  :key="item.src"
                  class="image-content"

                <v-touch 
                  tag="div"
                  @tap="onTap(index)"
                >
                <img 
                 
                 
                  class="scale-icon"
                />
                </v-touch>

                <!-- <div class="swiper-lazy-preloader">loading...</div> -->
            </div>
          </div>
      </div>
    </div>

style.less:

.swiper-container {
      width: 100%;
      height:100%;
    }
      
    .swiper-container > .swiper-wrapper{
      /**Active pictures have shadows*/
      overflow-y:visible;
      /**
      *Adjust the offset for the swiper
      *Pre / next container frame leakage: 20
      *Margin of design draft: 17.5
      * 17.5 + 20 = 37.5px
      */
      // margin-left:-0.375rem;
    }
  
    .swiper-slide {
      position:relative;
      box-sizing: border-box;
      padding:0.3rem 0.3rem 0.9rem 0.3rem;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: 300ms;
      width:3rem;
      height:4.25rem;
      transform: scale(0.74);
      background-image:url('../../../assets/art-picture/pic-border.png');
      background-repeat:  no-repeat;
      background-position:center ;
      background-size:100% 100%;
      img[lazy="loading"] {
        width: 0.3rem;
        height:0.3rem;
      }
    }
    .swiper-slide:not(.swiper-slide-active){
      /**
       *Calculate the margin error caused by scale 
       *Zoom of design draft: 0.74 container width / screen width of design draft currently playing: 300 / 375
       *Error generated by scale: 300 * (1-0.74) = 78px
       *Standard value 17.5
       *Elimination error: 78 / 2 - 17.5 / 2 = 30.25px;
       */
      // margin-left:-0.3025rem; 
      // margin-right:-0.3025rem;
      /**
      *Calculate the error caused by background shadow when vertically centered
      *Actual position: 365 * 0.74/2 = 135.5
      *Current position: 425 * 0.74/2 = 157.25
      *Error: (157.25 - 135.5) / 2 = 11.1px
      */
      margin-top:-0.111rem;
    }
  
    .swiper-slide-active{
      transform: scale(1);
      width: 3rem !important;
      height:4.25rem !important;
      /**
      *Eliminate the error of margin calculation on the current playback container
      *Margin effect: 78 / 2 - 30.25
      *Elimination error: 17.5 - (78 / 2 - 30.25) = 8.75px
      */
      // margin-left:0.0875rem !important;
      // margin-right:0.0875rem !important;
    }

js:

...

  //Some configurations that need to be changed
  get parameters() {
    const parameters = {
      spaceBetween: -0.225 * this.rootFontSize,
      slidesOffsetBefore: 0.375 * this.rootFontSize,
      slidesOffsetAfter: -0.375 * this.rootFontSize,
      width: 3 * this.rootFontSize,
      height: 4.25 * this.rootFontSize,
      observer: true // handle async
    };
    if (this.images.length === 1) {
      parameters.spaceBetween = 0;
    }
    return parameters;
  }

  public mounted() {
    this.initRootFontSize();
    this.initSwiper({
      initialSlide: this.index
    });
  }

  // get rem(root font-size)
  public initRootFontSize() {
    const html = document.getElementsByTagName('html')[0];
    const rootFontSizeStr = html.style.fontSize;
    this.rootFontSize = parseFloat(rootFontSizeStr);
  }

  public initSwiper(params= {}) {
    const mySwiper = new Swiper('.swiper-container', {
      slidesPerView: 1,
      loop: false,
      preloadImages: false, // preload images:false
      lazy: false, // lazy load images
      on: {
        slideChange: this.onSlideChange,
        sliderMove: () => {
          if (this.slideStatus !== 'fadeInOut') {
            this.slideStatus = 'fadeInOut';
          }
        },
        touchEnd: () => {
          this.slideStatus = 'fadeOut';
        },
      },
      ...this.parameters,
      ...params
    });
    this.mySwiper = mySwiper;
  }

  ...

Trampled pit

Problems with scale:

  • Although scale will reduce the element size, the original size space will not be released. Can refer tozoom vs transform:scaleThis creates a scaling gap between the elements
  • slidesPerView: NumberIf the configuration property is set to a numerical value, it is likely that the central page will not appear well in the central page
  • loop:trueWhen, the third node will be copied. In other words, there are 10 items in the list, and there will be 3 * 10 = 30 nodes. When there are many nodes, it will affect the performance
  • loop:trueWhen switching between the beginning and the end, clicking swiper will not trigger the click event. Click will not be triggered on the swiper slide
  • on:tap(swiper,event)This is triggered by clicking on any page. However, if the event test on the PC is excellent (including the path attribute), there is only istrud on the real machine. You can’t know which element the user specifically ordered

Solution I

  • usemargin-left: x<0;margin-right<0To eliminate zoom spacing [there will be problems with horizontal and vertical screen switching]
  • slidesPerView:'auto'Better use
  • prohibitloop:true
  • useon:tapNeed to be careful, real machine testing is indispensable

Perfect realization of abnormity (support horizontal and vertical screen switching)

Although the negative margin is set through the above margin to achieve the special-shaped effect visually, the problem of changing the style is found during the horizontal and vertical screen switching.

Reason: the way you modify the swiper wrapper and slider by using CSS style will not be counted by swiperjs, resulting in problems in the calculation of translate.

Solution 1: center the activity card by yourself

{
  slidesPerView: 1,
  loop: false,
  preloadImages: false, // preload images:false
  lazy: false, // lazy load images
}

Solution 2: how to remove sclae margin without CSS style

Implementation of special-shaped scrolling with swiper

After use, it is found that this will be set to the slider: space between. You can use this instead of the previous CSS margin setting, and the distance will be calculated by swiepr.

Centeredslides is left by default. How to solve the centering effect of the first card?

Implementation of special-shaped scrolling with swiper

Therefore, after calculating the value, you can set it:

{
  spaceBetween: -0.3025 * this.rootFontSize,
  slidesOffsetBefore: 0.375 * this.rootFontSize,
  slidesOffsetAfter: -0.375 * this.rootFontSize,
}

Why set slidesoffsetafter?
If it is not set, the border bug will appear when dragging left and right.

Solution 3: screen size adaptation (screen of different models | horizontal and vertical screen switching)

  • The value swiper set in solution 2 only supports Px, so REM must be set   → px
  • You need to get the latest REM and update the swiper when the horizontal and vertical screens are switched
public mounted() {
    this.initRootFontSize();
    this.initSwiper();
    window.addEventListener('resize', this.resizeHandler, false);
  }


  public initRootFontSize() {
    const html = document.getElementsByTagName('html')[0];
    const rootFontSizeStr = html.style.fontSize;
    this.rootFontSize = parseFloat(rootFontSizeStr);
  }  

  public resizeHandler() {
    this.initRootFontSize();
    /** update swiper params */
    this.mySwiper.params.spaceBetween = -0.3025 * this.rootFontSize;
    this.mySwiper.params.slidesOffsetBefore = 0.375 * this.rootFontSize;
    this.mySwiper.params.slidesOffsetAfter = -0.375 * this.rootFontSize;
    this.mySwiper.update();//  Key APIs
  }

In this way, the layout display is no problem, but for special-shaped cards, the width and height of inactive cards may change. If you have strict requirements on the width and height of the slider, you need to specify the width and height in the configuration:


{
  height:4.25 * 
this.rootFontSize,
  width:3 * this.rootFontSize,
}

You can extract functions that change configuration:

//Some configurations that need to be changed
  get parameters() {
    return {
      spaceBetween: -0.225 * this.rootFontSize,
      slidesOffsetBefore: 0.375 * this.rootFontSize,
      slidesOffsetAfter: -0.375 * this.rootFontSize,
      width: 3 * this.rootFontSize,
      height: 4.25 * this.rootFontSize,
      observer: true // handle async
    };
  }

  public initSwiper(params= {}) {
    const mySwiper = new Swiper('.swiper-container', {
      slidesPerView: 1,
      loop: false,
      preloadImages: false, // preload images:false
      lazy: false, // lazy load images
      on: {
        slideChange: this.onSlideChange
      },
      ...this.parameters,
      ...params
    });
    this.mySwiper = mySwiper;
  }

  public resizeHandler() {
    this.initRootFontSize();
    /** update swiper params */
    Object.keys(this.parameters).forEach(key => {
      const value = this.parameters[key];
      this.mySwiper.params[key] = value;
    });
    this.mySwiper.update();
  }

Solution 4: asynchronously loading data and card layout

Usually, the list is obtained from the interface and then set. You may encounter the problem of abnormal card layout display. At this time, you can try the following in the configuration:

{
 observer:true
}

This will update the element layout.

Solution 5: after the above four steps are completed, the leaflet shows the problem of leaning to the left

//Handle the style problem of pasting left when there is only one picture
    if (images.length === 1) {
      this.mySwiper.params.spaceBetween = 0;
      this.mySwiper.update();
    }

summary

Solutions to achieve the perfect opposite sex:

  • spaceBetween/slidesOffsetBefore/slidesOffsetAfter
  • observer: true

optimization

  • v-lazy
  • v-touch