A similar time-selection plugin was first implemented, but the scope was too narrow, so recently the implementation was released and a high-reuse VUE component was rewritten.

Support Android 4.0 or above, Safari 7 or above

Results the preview

gitHub

The roller part is the main DOM structure



<template>
  <div class="pd-select-item">
    <div class="pd-select-line"></div>
    <ul class="pd-select-list">
      <li class="pd-select-list-item">1</li>
    </ul>
    <ul class="pd-select-wheel">
      <li class="pd-select-wheel-item">1</li>
    </ul>
  </div>
</template>Copy the code

props



 props: {
      data: {
        type: Array,
        required: true
      },
      type: {
        type: String,
        default: 'cycle'
      },
      value: {}
    }Copy the code

Set the CSS style to center vertically



.pd-select-line..pd-select-list..pd-select-wheel {
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(50%); }.pd-select-list {
    overflow: hidden;
}Copy the code

Scroll wheel 3D style Settings



/* Wheel box */
.pd-select-wheel {
    transform-style: preserve-3d;
    height: 30px;
}
/* * * */
.pd-select-wheel-item {
    white-space: nowrap;
    text-overflow: ellipsis;
    backface-visibility: hidden;
    position: absolute;
    top: 0px;
    width: 100%;
    overflow: hidden;
}Copy the code

Transform-style: preserve-3d; backface-visibility: hidden; The first one is the 3D layout to make the interface 3D, and the second one is to make the back of the scroll wheel automatically hide (the red part of the image above, the DOM node on the back will automatically hide).

How to implement 3D roller

Rotate3d (1, 0, 0, x deg); Rotate3d (1, 0, 0, xDEG) translate3D (0px, 0px, [x]px);



The top 2 images show translate3D (0px, 0px, [x]px); The effect of this statement [x] is the radius of the circle

As you can see from the image above, we simply rotate each DOM by itself and then use translate3D (0px, 0px, [x]px); Each DOM is expanded to form a circle. Alpha is the rotation Angle of each DOM itself, and since only 0 to 180 degrees are used here, a box is used to hold the DOM

Row height and Angle calculation



Given the Angle between the two sides, calculate the length of the third side ~=34px

tool.520101.com/calcul…

Infinite roller implementation



/* Scroll wheel display size limit */
spin: {start: 0.end: 9.branch: 9}

/* Get spin data */
 getSpinData (index) {
   index = index % this.listData.length
   return this.listData[index >= 0 ? index : index + this.listData.length]
 }
 /* The modulo takes the index of the array and forms a circleCopy the code

Touchend is special

Set the setCSS type in TouchEnd to round up the scrolling data so that when you stop it, it’s exactly rotated into place, one by one



 // other code ....
 /* Calculates the integer distance touchEnd moves */
        let endMove = margin
        let endDeg = Math.round(updateDeg / deg) * deg
        if (type === 'end') {
          this.setListTransform(endMove, margin)
          this.setWheelDeg(endDeg)
        } else {
          this.setListTransform(updateMove, margin)
          this.setWheelDeg(updateDeg)
        }
  // other code ....Copy the code

Inertial slow



// other code ....
setWheelDeg (updateDeg, type, time = 1000) {
        if (type === 'end') {
          this.$refs.wheel.style.webkitTransition = `transform ${time}Ms Cubic - Bezier (0.19, 1, 0.22, 1) '
          this.$refs.wheel.style.webkitTransform = `rotate3d(1, 0, 0, ${updateDeg}deg)`
        } else {
          this.$refs.wheel.style.webkitTransition = ' '
          this.$refs.wheel.style.webkitTransform = `rotate3d(1, 0, 0, ${updateDeg}deg)`
        }
      }
setListTransform (translateY = 0, marginTop = 0, type, time = 1000) {
        if (type === 'end') {
          this.$refs.list.style.webkitTransition = `transform ${time}Ms Cubic - Bezier (0.19, 1, 0.22, 1) '
          this.$refs.list.style.webkitTransform = `translateY(${translateY - this.spin.branch * 34}px)`
          this.$refs.list.style.marginTop = `${-marginTop}px`
          this.$refs.list.setAttribute('scroll', translateY)
          console.log('end')}else {
          this.$refs.list.style.webkitTransition = ' '
          this.$refs.list.style.webkitTransform = `translateY(${translateY - this.spin.branch * 34}px)`
          this.$refs.list.style.marginTop = `${-marginTop}px`
          this.$refs.list.setAttribute('scroll', translateY)
        }
}
// other code ....Copy the code

Gets the currently selected value




 /* Get the value */ after setting the CSS
 
setStyle (move, type, time) {
   / /... other code
   /* Set the $emit delay */
   setTimeout((a)= > this.getPickValue(endMove), 1000)
  / /... other code
}

/* Get the selected value */
      getPickValue (move) {
        let index = Math.abs(move / 34)
        let pickValue = this.getSpinData(index)
        this.$emit('input', pickValue)
      }Copy the code

Initialization Settings



 mounted () {
      /* Event binding */
      this.$el.addEventListener('touchstart'.this.itemTouchStart)
      this.$el.addEventListener('touchmove'.this.itemTouchMove)
      this.$el.addEventListener('touchend'.this.itemTouchEnd)
      /* Initialization state */
      let index = this.listData.indexOf(this.value)
      if (index === - 1) {
        console.warn('Current initial value does not exist, please check post listData range!! ')
        this.setListTransform()
        this.getPickValue(0)}else {
        let move = index * 34
        /* because it slides up, it's minus */
        this.setStyle(-move)
        this.setListTransform(-move, -move)
      }Copy the code

When shown as a non-infinite scroll wheel

We can’t scroll more than the original array length *34 and not less than 0.



/* Determine the maximum distance of updateMove based on the roller type line or cycle */
        if (this.type === 'line') {
          if (updateMove > 0) {
            updateMove = 0
          }
          if (updateMove < -(this.listData.length - 1) * singleHeight) {
            updateMove = -(this.listData.length - 1) * singleHeight
          }
        }
 /* Control the scroll wheel display according to type */
      setHidden (index) {
        if (this.type === 'line') {
          return index < 0 || index > this.listData.length - 1
        } else {
          return false}},Copy the code

The DOM structure also adds the corresponding response



<div class="pd-select-item">
    <div class="pd-select-line"></div>
    <div class="pd-select-list">
      <ul class="pd-select-ul" ref="list">
        <li class="pd-select-list-item" v-for="el,index in renderData " :class="{'hidden':setHidden(el.index)}" :key="index">{{el.value}}</li>
      </ul>
    </div>
    <ul class="pd-select-wheel" ref="wheel">
      <li class="pd-select-wheel-item" :class="{'hidden':setHidden(el.index)}" :style="setWheelItemDeg(el.index)" :index="el.index" v-for="el,index in renderData " :key="index">{{el.value}}</li>
    </ul>
  </div>Copy the code

If you don’t understand the place, please leave a comment below, or [email protected]

There is room for code optimization. Thank you