preface

Recently the interview found that many front-end programmers have never written plug-in experience, basically are online Baidu. So I’m going to write a series of articles that will teach some of you how to write plug-ins without writing components. This series of articles is based on VUE, the core content is the same, you can quickly adapt to react, Angular, or applets components. This is the first article about a side menu component similar to QQ. Github source code (if you don’t bother to help start, please everyone big ye admire a star); The demo show.

Results show

First let us see an effect display, know what we are going to do is a look like, the picture is a little fuzzy, we will do:

Started making

DOM structure

There should be two containers in the overall structure: 1. Menu container 2. So the current DOM structure is as follows:

<template>
  <div class="r-slide-menu">
    <div class="r-slide-menu-wrap"></div>
    <div class="r-slide-menu-content"></div>
  </div>
</template>
Copy the code

To make the menu content and theme content customizable, we add two more slot slots to the two containers: the default slot for the body content and the menu slot for the menu:

<template>
  <div class="r-slide-menu">
    <div class="r-slide-menu-wrap">
      <slot name="menu"></slot>
    </div>
    <div class="r-slide-menu-content">
      <slot></slot>
    </div>
  </div>
</template>
Copy the code

CSS styles

SCSS was used in my project, and the code is as follows:

<style lang="scss">
@mixin one-screen {
  position: absolute;
  left:0;
  top:0;
  width:100%;
  height:100%;
  overflow: hidden;
}

.r-slide-menu{
  @include one-screen;
  &-wrap, &-content{
    @include one-screen;
  }
  &-transition{
    -webkit-transition: transform .3s;
    transition: transform .3s;
  }
}
</style>
Copy the code

Now we have two absolutely positioned containers

javascript

Now the formal code writing begins, first let’s clarify the interaction logic:

  1. Both the body container and the menu container move as the finger slides left and right
  2. The page cannot continue to slide to the right when the finger moves beyond the width of the menu container
  3. A page cannot move further to the left when a finger moves to the left causing the distance between the menu and the page to zero
  4. When the finger is released away from the screen, the page slides over a certain distance (the ratio of the width of the entire menu) to open the entire menu, if less than a certain distance to close the menu

So now we need to be able to use components to customize the width of the menu and the ratio of the threshold that triggers menu closure to the width of the menu, and we need to add touch events to the body container, Finally, we add a style to control the movement of the menu container and the body container, and control the movement of the container by controlling the style

<template> <div class="r-slide-menu"> <div class="r-slide-menu-wrap" :style="wrapStyle"> <slot name="menu"></slot> </div> <div class="r-slide-menu-content" :style="contentStyle" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"> <slot></slot> </div> </div> </template> <script> export default { props: { width: { type: String, default: '250' }, ratio: { type: Number, default: 2 } }, data () { return { isMoving: false, transitionClass: '', startPoint: { X: 0, y: 0 }, oldPoint: { x: 0, y: 0 }, move: { x: 0, y: 0 } } }, computed: { wrapStyle () { let style = { width: '${this.width}px', // initially offset the menu to the left by a certain percentage, and then correct the offset by 1/2 of the default width as it moves, so that the main container moves 2 pixels and the menu moves 1 pixel during the movement, leaving a parallax animation: `-${this.width / this.ratio}px`, transform: `translate3d(${this.move.x / this.ratio}px, 0px, 0px)` } return style }, contentStyle () { let style = { transform: `translate3d(${this.move.x}px, 0px, 0px)` } return style } }, methods: { touchstart (e) {}, touchmove (e) {}, touchend (e) {} } }Copy the code

Next, let’s implement our core touch event handler. The event logic is as follows:

  1. The moment the finger is pressed, the point touched by the current finger and the current position of the main container are recorded
  2. As you move your finger, get the position of the moving point
  3. Calculate the axis distance of X and Y moving at the current finger point. If the distance of X moving is greater than the distance of Y moving, it is judged as horizontal movement, otherwise, it is vertical movement
  4. If the movement is horizontal, judge that the current movement distance is within the reasonable movement range (0 to menu width). If so, change the position of the two containers (prevent other events in the page from being triggered during the movement).
  5. Finger off screen: If the accumulated movement distance exceeds the critical value, use animation to open the menu, otherwise close the menu
touchstart (e) { this.oldPoint.x = e.touches[0].pageX this.oldPoint.y = e.touches[0].pageY this.startPoint.x = this.move.x this.startPoint.y = this.move.y this.setTransition() }, touchmove (e) { let newPoint = { x: e.touches[0].pageX, y: e.touches[0].pageY } let moveX = newPoint.x - this.oldPoint.x let moveY = newPoint.y - this.oldPoint.y if (Math.abs(moveX) < Math.abs(moveY)) return false e.preventDefault() this.isMoving = true moveX = this.startPoint.x * 1 +  moveX * 1 moveY = this.startPoint.y * 1 + moveY * 1 if (moveX >= this.width) { this.move.x = this.width } else if (moveX <= 0) { this.move.x = 0 } else { this.move.x = moveX } }, touchend (e) { this.setTransition(true) this.isMoving = false this.move.x = (this.move.x > this.width / this.ratio) ? this.width : 0 }, setTransition (isTransition = false) { this.transitionClass = isTransition ? 'r-slide-menu-transition' : '' }Copy the code

The setTransition function adds the transition property to the container element when the finger leaves. The transition animation closes and opens the container. So you need to remove the transition property from the container as soon as you press it down to avoid the bad experience of the container and finger sliding delay. As a final note, the reason for using Translate3D instead of Translate in the code is to enable 3D acceleration of animation on mobile phones and improve animation fluency. The final code is as follows:

<template> <div class="r-slide-menu"> <div class="r-slide-menu-wrap" :class="transitionClass" :style="wrapStyle"> <slot name="menu"></slot> </div> <div class="r-slide-menu-content" :class="transitionClass" :style="contentStyle" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"> <slot></slot> </div> </div> </template> <script> export default { props: { width: { type: String, default: '250' }, ratio: { type: Number, default: 2 } }, data () { return { isMoving: false, transitionClass: '', startPoint: { X: 0, y: 0 }, oldPoint: { x: 0, y: 0 }, move: { x: 0, y: 0 } } }, computed: { wrapStyle () { let style = { width: `${this.width}px`, left: `-${this.width / this.ratio}px`, transform: `translate3d(${this.move.x / this.ratio}px, 0px, 0px)` } return style }, contentStyle () { let style = { transform: `translate3d(${this.move.x}px, 0px, 0px)` } return style } }, methods: { touchstart (e) { this.oldPoint.x = e.touches[0].pageX this.oldPoint.y = e.touches[0].pageY this.startPoint.x = this.move.x this.startPoint.y = this.move.y this.setTransition() }, touchmove (e) { let newPoint = { x: e.touches[0].pageX, y: e.touches[0].pageY } let moveX = newPoint.x - this.oldPoint.x let moveY = newPoint.y - this.oldPoint.y if (Math.abs(moveX) < Math.abs(moveY)) return false e.preventDefault() this.isMoving = true moveX = this.startPoint.x * 1 +  moveX * 1 moveY = this.startPoint.y * 1 + moveY * 1 if (moveX >= this.width) { this.move.x = this.width } else if (moveX <= 0) { this.move.x = 0 } else { this.move.x = moveX } }, touchend (e) { this.setTransition(true) this.isMoving = false this.move.x = (this.move.x > this.width / this.ratio) ? This.settransition (true) this.move. X = (this.move. X === 0)? this.width : 0 }, setTransition (isTransition = false) { this.transitionClass = isTransition ? 'r-slide-menu-transition' : '' } } } </script> <style lang="scss"> @mixin one-screen { position: absolute; left:0; top:0; width:100%; height:100%; overflow: hidden; } .r-slide-menu{ @include one-screen; &-wrap, &-content{ @include one-screen; } &-transition{ -webkit-transition: transform .3s; transition: transform .3s; } } </style>Copy the code

Write in the last

This is the first time to write such dry goods, please forgive me for writing bad, if you think it is useful, give me a reward to drink a cup of tea, so that I will be more motivated to write all mobile terminal COMMONLY used UI components of the article.