This is the 24th day of my participation in the August More Text Challenge

To introduce

This component was used in the company’s project. When I accepted the project, I only completed the animation of pull-down and pull-down refresh. Later, the boss felt that it was not nice to slide to the bottom without interaction, so he added the pull-up animation to improve the user experience and feedback the information to the bottom.

Light said may not express clearly, see a wave of GIF.

You can clearly see the effects of pull-down, pull-down refresh diagram, loading animation, and pull-up respectively.

Since it is a company project, there will not be all the code posted here, nor is it a standard component development idea, so the article mainly explains the core idea and componentization part.

Let’s start with structure.

The content section must be a slot for the development section to render the list. Components mainly include pull-down, pull-down refresh event feedback and pull-up.

Transform: translateY()+ Tranition: transform.2s ease-out

The code for the structure section is as follows

<template>
  <view>
    <view class="refresh-box" @touchstart.stop="refresh.controlTouchstart" @touchmove.stop="refresh.controlTouchmove" @touchend.stop="refresh.controlTouchend">
      <view class="load-more-box">
        <! Refresh the image area -->
        <view class="load-more-control" 
        :style="{ minHeight: 140 + refreshTop + 'rpx' }">
          <! -- Loading icon -->
          <uarea-icons class="refresh-icon" type="home-refresh" color="#c8c8c8" size="30" />
          <view class="control-title">Release can refresh rent</view>
	</view>
        <view class="load-more-dots hollow-dots-spinner">
          <! -- Loading animation -->
	  <view class="dot" :style="[{ animationPlayState: playState }]"></view>
	  <view class="dot" :style="[{ animationPlayState: playState }]"></view>
	  <view class="dot" :style="[{ animationPlayState: playState }]"></view>
	</view>
      </view>
      <! -- Content area -->
      <view style="width: 100%;"><slot /></view>
    </view>
  </view>
</template>
Copy the code

The structure is relatively simple, but can be seen in the structure, mainly through the touch event to control the user’s finger situation

Because the company project is the primary mobile, the code is mobile-biased, but only slightly tweaked, mainly the logic.

The logical part

The logic part is more, but the code about the pull up and down part is relatively simple, mainly for the processing of three touch events.

controlTouchstart(e) {
	if (!this.isRefresh || this.isCurrentRequest) {
		return;
	}
	this.fingerLeave = false;
	// Clean up timer
	this.fingerNum = e.touches.length;
	clearTimeout(this.waitMoveDistanceTimer);
	this.waitMoveDistanceTimer = 0;
	clearTimeout(this.refreshEndTimer);
	this.refreshEndTimer = 0;
	this.touchClient.startY = e.touches.slice(-1) [0].clientY;
}
controlTouchmove(e) {
	if (!this.isRefresh || this.isCurrentRequest) {
		return;
	}
	this.touchClient.endY = e.touches.slice(-1) [0].clientY;
	this.moveDistance = this.touchClient.endY - this.touchClient.startY;
	this.touchClient.startY = this.touchClient.endY;
	if (this.distance < (this.allDistance * 0.94)) {
		this.dragNum = 0.45 * (1 - (this.lastMoveDistance / (this.allDistance * 0.94)));
		this.distance = Number((this.lastMoveDistance + Math.floor(this.dragNum * this.moveDistance)).toFixed(
			2));
		this.lastMoveDistance = this.distance;
	} else {
		this.distance = Number((this.allDistance * 0.94).toFixed(2));
	}
	// This is used to calculate the Angle of the wheel
	this.tempDistance = Number((this.tempLastMoveDistance + Math.floor((this.dragNum <= 0.1 ? 0.1 : this
			.dragNum) *
		this.moveDistance)).toFixed(2));
	this.tempLastMoveDistance = this.tempDistance;
	/ / show the load - more - control
	if (this.distance <= 0) {
		if (e.touches.length < 2) {
			this.$emit('disableScroll'.false);
		}
		this.dom.getElementsByClassName('refresh-box') [0].style.transform = `translateY(0px)`;
		this.dom.getElementsByClassName('refresh-box') [0].classList.remove('refresh-box-transition');
		// Unroll forbidden scrolling
		this.distance = 0;
		this.moveDistance = 0;
		this.lastMoveDistance = 0;
		this.dom.getElementsByClassName('load-more-box') [0].style.top = up2rpx(-140 - this.refreshTop, 1) + "px";
		this.dom.getElementsByClassName('refresh-icon') [0].style.transform = "rotate(0deg)";
		this.dom.getElementsByClassName('load-more-control') [0].style.height = up2rpx(140 + this.refreshTop, 1) + "px";
		return;
	} else {
		// #ifdef H5
		e.preventDefault();
		// #endif
		this.startRefresh = true;
		this.$emit('disableScroll'.true);
		if (this.distance > 30) {
			// Lock prevents scrolling
			this.dom.getElementsByClassName('load-more-control') [0].classList.add('load-more-transition');
			if ((this.distance - 30) / 10 < 1) {
				this.dom.getElementsByClassName('load-more-control') [0].style.opacity = (this.distance -
					30) / 10;
			} else {
				this.dom.getElementsByClassName('load-more-control') [0].style.opacity = 1; }}else {
			this.dom.getElementsByClassName('load-more-control') [0].classList.remove('load-more-transition');
			this.dom.getElementsByClassName('load-more-control') [0].style.opacity = 0; }}if (this.distance > up2rpx(140.1)) {
		this.dom.getElementsByClassName('load-more-control') [0].style.height = `The ${this.distance+up2rpx(this.refreshTop,1)}px`;
		this.dom.getElementsByClassName('load-more-box') [0].style.top = ` -The ${this.distance+up2rpx(this.refreshTop,1)}px`;
	}
	this.dom.getElementsByClassName('refresh-box') [0].style.transform = `translateY(The ${this.distance}px)`;
	this.dom.getElementsByClassName('refresh-icon') [0].style.transform = `rotate(The ${360*2*this.tempDistance/this.allDistance}deg)`;
}
controlTouchend(e, instance) {
	if (!this.fingerLeave) {
		this.fingerLeave = true;
		this.isRefresh = true;
	}
	if (!this.isRefresh || this.isCurrentRequest || !this.startRefresh) {
		return;
	}
	if (e.touches.length == 1&& -this.fingerNum > 0) {
		return;
	}

	let refreshTop = this.refreshTop;
	if (this.distance <= 30) {
		// Trigger animation when rebounding
		this.distance = 0;
		this.moveDistance = 0;
		this.lastMoveDistance = 0;
		this.startRefresh = false;
		this.dom.getElementsByClassName('refresh-box') [0].classList.add('refresh-box-transition');
		this.dom.getElementsByClassName('refresh-icon') [0].classList.add('control-icon');
		this.dom.getElementsByClassName('load-more-control') [0].classList.add('load-more-transition');
		// Close the animation after 0.1s
		setTimeout(() = > {
			if (this.dom.getElementsByClassName('refresh-box') [0]) {
				this.dom.getElementsByClassName('refresh-box') [0].classList.remove('refresh-box-transition');
				this.dom.getElementsByClassName('load-more-control') [0].classList.remove( 'load-more-transition');
			}
			if (this.dom.getElementsByClassName('refresh-icon') [0]) {
				this.dom.getElementsByClassName('refresh-icon') [0].classList.remove('control-icon'); }},100)
		return;
	}
	this.dom.getElementsByClassName('refresh-box') [0].classList.add('refresh-box-transition');
	// Set the release time to 200ms
	this.waitMoveDistanceTimer = setTimeout(() = > {
		this.$emit('disableScroll'.false);
		this.startRefresh = false;
		this.isCurrentRequest = true;
		if (this.isHome) {
			this.dom.getElementsByClassName('refresh-box') [0].style.transform = `translateY(${up2rpx(100-refreshTop,1)}px)`;
		} else {
			this.dom.getElementsByClassName('refresh-box') [0].style.transform = `translateY(${up2rpx(100-refreshTop*0.5.1)}px)`;
		}
		// Enable the request loading animation
		instance.callMethod('changePlayState'.'running');

		clearTimeout(this.waitMoveDistanceTimer);
		this.waitMoveDistanceTimer = 0;
	}, 200)}Copy the code

The code is long, the rules are mostly for ICONS, and the scale is cost-effective, after all, you can’t pull as much as the user moves, that would lead to too much movement. So there is a lot of conversion in move events.

However, in touchend, style processing is mainly used, and style processing of moving distance and loading part is cleared.