Image rotation is a very common scene and function. This function will be used in the rotation banner of the home page of mobile websites and the commodity picture of the commodity leisure page

A common scenario feature like this must have been written by someone already, so there are three common steps to follow in this scenario:

  • Open the refrigeratorStart making
  • searchswiper,slider,AlbumKey words such as
  • Find the library you want,npm installthe

There is nothing wrong with this approach. There are wheels available, of course, because the project uses VUE, so I searched the Internet for vUe-based rote component libraries and found two libraries that I am satisfied with: VUe-awes-swiper and Vue-Swipe

  • vue-awesome-swiper

This library is widely used by well-known castings frameworks. It has rich functions and is suitable for various castings scenarios, such as left and right buttons, dynamic indicator points, progress bar indicators, vertical switching, and multiple slides displayed at a time. Function is not too perfect but I just want to use a few basic functions, so many functions for me is not merely to see the document difficult, more to the point will introduce too many redundant code in the project, very not easy come down through various means to code volume and the result is because of the introduction of a package back to before liberation, bad objectionable

  • vue-swipe

A library produced by the ele. me front end team is relatively lean and has very little code, but it is too lean. For example, it does not support infinite rotation, it does not support custom swiperItem, and it always feels a little stiff

As for the other libraries that I can search, they are not famous or too small to be easily introduced into the production environment, so I decided to build a wheel to solve this problem, so that the functions and code volume of the group price library can be controlled by myself, and even if there are any bugs, I can quickly fix them by myself

Let’s take a look at the final implementation:

Or if you want to try it out for yourself, there’s also a written Demo

I have packaged this function into an NPM package, which can be downloaded, installed and used directly. The size of the code including the style is less than 18KB after compression, and the size of the code after Gzipped is less than 7KB. The source code has been uploaded

Sliding form

For the sake of description, we will define the terms. We will call each slide item swiperItem and the container that holds all the slides swiper:

At present, most sliding component libraries implement component sliding in two ways

The first option is to render only three swiperitems at a time and update them immediately after each swipe to the next swiperItem

The advantage of this approach is that no matter how many swiperitems there are, the browser will render only three of them at a time. The disadvantage is that if there are already fewer than three swiperitems, additional processing is required. And because you can only swipe one swiperItem at a time, it’s not as smooth to use, as Vue-Swipe does

Second, render all swiperItems at once, and sometimes add two more swiperItems at the beginning and end of the original swiperItem for a smoother experience. For example, if the original swiperItem is 1, 2, 3, 4, 5, After processing, it becomes 5, 1, 2, 3, 4, 5, 1, VUe-awesome-swiper uses this method

The advantage is that it is more smooth to use. The disadvantage is that if there is a large amount of data, such as hundreds or thousands of data, it will affect the rendering performance of the browser. However, in general, there is not such a large amount of data

After comprehensive consideration, I decided to adopt the second one

The data processing

This component library provides two ways to pass in swiperItem data

  • The first is straight throughpropsPass in an array of images

Generally speaking, the main element of a multicast component is just a presentation image, so passing an array of images directly through props will suffice for most of the requirements

<swiper :urlList="urlList" />
Copy the code

Append to and append to an array in this case is simple:

this.currentList = this.urlList.length > 1
  ? this.urlList.slice(- 1).concat(this.urlList, this.urlList.slice(0.1)).map((url, index) = > ({ url, _id: index }))
  : this.urlList.map((url, index) = > ({ url, _id: index }))
Copy the code

Then render directly to the template:

<div class="img-box" v-for="item in currentList" :key="item._id" :style="{ backgroundImage: `url(${item.url})`, backgroundSize }"></div>
Copy the code

By the way, as for the layout of the image, I did not write an IMG element directly, but rendered the image as a background image. The advantage of this processing is that it is easy to achieve UI control over the length, width, size and position of the image. If you want the image to be fully displayed, you can use background-size: Display: flex display: flex display: flex display: flex display: flex display: flex display: flex display: flex display: flex display: flex display: flex display: flex display: flex display: flex display: flex display: contain background-size: cover , it is easy to have compatibility problems on some devices in some cases, directly background-position: 50%; done

By extension, when we meet some small icon layouts, we can also adopt this way, which is very easy to align. There is no need to adjust vertical-align slowly, and there will be no compatibility problems

  • The second is receptionswiperItemChild components

This gives developers a lot of room to customize the contents of swiperItem, not just a single image, but a bit of a hassle because slot is a component-level thing that can’t be handled dynamically. Yes, I can, but since we have already used the framework, it seems that the atmosphere is not right to change the DOM directly. I struggled until I came up with the dynamic component and the Render function

The main idea is to pass in a swiperItem as a slot and render it inside the parent swiper component, but at the same time, to render a component before and after the slot:

<swiper>
  <swiperItem />
  <swiperItem />
  <swiperItem />
</swiper>
Copy the code
<! -- This is the parent of swiper -->
<component :is="firstSwiperItem"></component>
<slot></slot>
<component :is="lastSwiperItem"></component>
Copy the code

FirstSwiperItem and lastSwiperItem are the two component dynamic components placed before and after slot. They are 5,1,2,3,4,5,1 and 1:

updateChild (slots) {
  this.firstSwiperItem = {
    render (h) {
      return h('div', {
        staticClass: 'swiper-item-box'
      }, slots.slice(- 1))}}this.lastSwiperItem = {
    render (h) {
      return h('div', {
        staticClass: 'swiper-item-box'
      }, slots.slice(0.1))}}}Copy the code

I originally wanted to do this with template, which was a bit easier, but since template would have to reference the full version of Vue that contains both the runtime and the compiler, it wasn’t cost-effective and wasn’t suitable for production, so I ended up with the Render function

Touch events

Listening to touch events and changing displacement in real time with Translate3D is the essence of sliding

The starting position coordinates are recorded in the TouchStart event, the distance difference is calculated in the Touchmove event for real-time position change, and the end is carried out in the TouchEnd

The logic is clear, but the details are a bit of a headache

For example, what if the user uses multiple fingers? What if I touchstart with two fingers and TOUCHmove with one finger? If the user first swipes left or right, how do you determine whether it is left or right compared to the initial swipes? If you swipe multiple swiperItems in a row, how do you decide whether to swipe left or right at the end?

These problems don’t exist if users follow best practices, but you can’t ask them to, so they have to be addressed

For multi-finger operations, I’ll stick with the last one in the E.Touches list:

stStartX = e.touches[touchCount - 1].clientX
Copy the code

Left slide right slide problem, the comparison of diffX and reference value criticalWidth, combined with sliding coordinate toX for double judgment, in the case of as little code as possible to draw the conclusion:

// diffX greater than 0 indicates right slide, less than 0 indicates left slide
if (diffX > 0) {
  stDirectionFlag = - 1
  stAutoNext = diffX > criticalWidth
  toX = stAutoNext ? -clientW * (activeIndex - 1) : -clientW * activeIndex
} else if (diffX < 0) {
  stDirectionFlag = 1
  stAutoNext = Math.abs(diffX) > criticalWidth
  toX = stAutoNext ? -clientW * (activeIndex + 1) : -clientW * activeIndex
} else {
  stDirectionFlag = 0
  stAutoNext = false
  toX = -clientW * activeIndex
}
Copy the code

SwiperItem swiperItem swiperItem swiperItem swiperItem swiperItem swiperItem swiperItem

// swiperItem is swiperItem
if (Math.abs(diffX) > clientW) {
  activeIndex = Math.ceil(-this.transX / clientW)
  diffX = diffX - clientW * wholeBlock
}
Copy the code

Closer to the original smooth experience

You can swipe a swiperItem, swipe it away, and the swiperItem will automatically slide to a fixed position, but you can interrupt this process by touching or swiping again. Change the original trajectory of swiperItem:

After a quick look, it seemed that vue-awesome swiper and Vue-Swipe didn’t offer this ability, but without it it didn’t feel like a smooth experience, so I decided to add it

For this function, we want to use JS to dynamically calculate the swiperItem action, and use requestAnimationFrame to simulate the animation effect of automatic swiperItem translate, so that we can easily obtain the value of swiperItem translate at any time. Then the ability to implement interception is simple

But later, considering that the cost performance of USING JS to simulate animation is too low, it is easy to encounter the situation of lag in the actual production process, so we turned to another implementation

The automatic sliding animation is handled by THE CSS, and the getBoundingClientRect API is used to retrieve the real-time position of the swiperItem when the finger touches it

The getBoundingClientRect API is compatible enough to be used in a real production environment, but considering that there are still some older devices that don’t support the API anyway, I’ve downgraded it as well:

const isSupportGetBoundingClientRect = typeof document.documentElement.getBoundingClientRect === 'function'
// ...
if (this.isTransToX) {
  if(! isSupportGetBoundingClientRect) {return touchStatus = 0
  }
  this.isTransToX = false
  this.transX = stPrevX = this.$refs.sliderWrapper.getBoundingClientRect().left - this.$refs.swiperContainer.getBoundingClientRect().left
}
Copy the code

conclusion

At the thought of themselves made wheels up, think of the wheels is easy, fast, day three days also slowly about, still really start development, only to find that it’s not that simple, because only work have time to do this thing, so eventually leng me for a week haven’t done, quickly finished the main body of code, But it takes most of the time to sort out the anomalies and test yourself, but you’re done anyway, right

Source code has been put on Github, code annotation is also more detailed, interested in can refer to the next, if there is any problem, welcome to mention issues