First, draw a disk

Implementation idea:

  • Divide the disc equally according to the number of prizes and calculate the Angle and approximate width of a prize
  • Set the midpoint of the bottom edge of each grid prize at the origin of the disk
  • According to the order of the calculation of the need to rotate the Angle of each grid prize, and rotate to their respective positions, spread full 360 degree disc
  • Position the pointer to the center of the disk

The prize data

const prizeList = [
  {
      name: "The prize 1".color: "red"
  },
  {
      name: "The prize 2".color: "orange"
  },
  {
      name: "The prize 3".color: "yellow"
  },
  {
      name: "The prize four.".color: "green"
  },
  {
      name: "Award 5".color: "hotpink"
  },
  {
      name: "The prize 6".color: "blue"
  },
  {
      name: "The prize 7".color: "purple"
  },
  {
      name: "The prize 8".color: "gray"}]data() {
   return {
      prizeLists: [].angleList: []}}Copy the code

Calculate the rotation Angle

initPrizeList() {
	const length = this.prizeList.length // Number of prizes
    const width = parseInt(3.14 * 300 / length) // The approximate width of each prize, since the lucky draw pointer will only be fixed in the middle of the prize, so it only needs to calculate the approximate width, there is overlap will not affect the page vision and results
    const halfWidth = -parseInt(width / 2) // The value that the initial position of the prize needs to deviate from
    const average = 360 / length // Divide the disk evenly, the Angle occupied by each prize
    const half = average / 2 // The middle position of each prize is also the Angle at which the pointer ends
    list.forEach((item, index) = > {
    	const angle = - (index * average + half) // Calculate the rotation Angle of each prize in turn, starting at 12 o 'clock
        item.style = `transform: rotate(${angle}deg); width: ${width}px; marginLeft: ${halfWidth}px` // First move all the prizes to the state of "lower midpoint to the origin of the disk", then rotate to the Angle calculated in the previous step
        this.angleList.push(index * average + half) // Collect the Angle in the middle of each prize
    })
    return [...list]
}

Copy the code

Page structure

<div class="lottery-wrap">
    <img class="lottery-pointer" :src="pointerImg" @click="beginRotate" alt="">
    <div class="prize-wrap" :style="rotateStyle">
        <div class="prize-list">
            <div class="prize-item" :style="item.style" v-for="(item, index) in prizeLists" :key="index">
                <span class="prize-icon" :style="{backgroundColor: item.color}"></span>
                <span class="prize-name">{{item.name}}</span>
            </div>
        </div>
    </div>
</div>
Copy the code
.lottery-wrap {
    width: 300px;
    height: 300px;
    border-radius: 50%;
    background-color: rgba(0.0.0.0.4);
    position: relative;
}
.lottery-pointer {
    width: 100px;
    height: 100px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2;
}
.prize-wrap {
    width: 100%;
    height: 100%;
}
.prize-list {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    .prize-item {
        position: absolute;
        // width: 120px;
        height: 150px;
        box-sizing: border-box;
        padding-top: 30px;
        top: 0;
        left: 50%;
        // margin-left: -60px;
        transform-origin: 50% 100%;
        display: flex;
        flex-direction: column;
        // justify-content: center;
        align-items: center;
        .prize-icon {
            width: 30px;
            height: 30px; }}}Copy the code

Now the structure of the disk is complete

Two, turn

Implementation idea:

  • When users click on the draw, they just need to turn the disk and keep the pointer still
  • After obtaining the prize returned by the back end in the real scene, the front end only needs to calculate the Angle and perform the rotation animation.
  • Each time a new Angle is evaluated, the page can be updated by evaluating properties
  • It should be noted that the rotation Angle should be saved after each rotation, and users should not click repeatedly

The initial data

data() {
	isRotating: false.// Prevent repeated click rotation
    rotateAngle: 0 // The current position
}
Copy the code

Spinning method

methods: {
	beginRotate() {
    	const { isRotating, rotateAngle, angleList } = this
        if (isRotating) reurn // Prevent repeated clicks
        this.isRotating = true
        const angle = rotateAngle + 5 * 360 + angleList[4] - (rotateAngle % 360) // For visual purposes, the default rotation is more than 5 turns
        this.rotateAngle = angle // Save the rotation Angle for the next rotation
        
        setTimeout(() = > {
        	this.isRotating = false // After this drawing is over, the next drawing can be carried out
            console.log("A winner! }}}, 5000)Copy the code

Evaluate the property update page

computed: {
	rotateStyle() {
    	return `
            transition: transform 5000ms ease-in-out;
            transform: rotate(The ${this.rotateAngle}deg)
        `}}Copy the code

So far completed the turntable lottery function

Next, transform to a more configurable component

Three, transformed into configurable components

Implementation idea: as far as possible to configure the data into parameters, including background, pointer pictures, prize list, rotation time, animation effect, rotation number, etc

Receive parameters

props: {
	prizeList: {
		type: Array.default: () = >[]},pointerImg: {
    	type: String.default: () = > require("./img/xxx.png")},lotteryBgImg: {
    	type: String.default: () = > require("./img/xxx.png")},duration: {
    	type: Number.default: () = > 5000
    },
    circle: {
    	type: Number.default: () = > 5
    },
    mode: {
    	type: String.default: () = > "ease-in-out"}}Copy the code

Transformation of rotation method

methods: {
	rotating(index) {
    	const { isRotating, duration, circle, count, rotateAngle, angleList } = this
        if (isRotating) reurn // Prevent repeated clicks
        this.isRotating = true
        const angle = rotateAngle + circle * 360 + angleList[index] - (rotateAngle % 360) // For visual purposes, the default rotation is more than 5 turns
        this.rotateAngle = angle // Save the rotation Angle for the next rotation
        
        setTimeout(() = > {
        	this.isRotating = false // After this drawing is over, the next drawing can be carried out
            console.log("A winner! }, duration) } }Copy the code

Modification of computing attributes

computed: {
	rotateStyle() {
    	return `
            transition: transform The ${this.duration}ms The ${this.mode};
            transform: rotate(The ${this.rotateAngle}deg)
        `}}Copy the code

In the actual scenario, it is safer to control the final prize by the back end, so it needs to be changed to be controlled by the parent component, while the click event is in the self component, so the following changes are made

<div class="lottery-wrap">
    <img class="lottery-pointer" :src="pointerImg" @click="beginRotate" alt="">
    <div class="prize-wrap" :style="rotateStyle">
        <div class="prize-list">
            <div class="prize-item" :style="item.style" v-for="(item, index) in prizeLists" :key="index">
                <span class="prize-icon" :style="{backgroundColor: item.color}"></span>
                <span class="prize-name">{{item.name}}</span>
            </div>
        </div>
    </div>
</div>
Copy the code
methods: {
	beginRotate() {
    	this.$emit("beginRotate") // Let the parent control the click and pass in the winning result
    },
    rotating(index) {
    	const { isRotating, duration, circle, count, rotateAngle, angleList } = this
        if (isRotating) reurn // Prevent repeated clicks
        this.isRotating = true
        const angle = rotateAngle + circle * 360 + angleList[index] - (rotateAngle % 360) // For visual purposes, the default rotation is more than 5 turns
        this.rotateAngle = angle // Save the rotation Angle for the next rotation
        
        setTimeout(() = > {
        	this.isRotating = false // After this drawing is over, the next drawing can be carried out
            this.rotateOver()
        }, duration)
    },
    rotateOver() {
    	this.$emit("rotateOver") // Notify the parent when the lottery ends}}Copy the code
/ / the parent component<turntable ref="lottery" :prizeList="prizeList" @beginRotate="beginRotate" @rotateOver="rotateOver"></turntable>
Copy the code
methods: {
	beginRotate() {
    	// Call the backend interface here to get the final award result
        const res = .......
        this.$refs.lottery.rotating(res)
    },
    rotateOver() {
    	// Complete the remaining steps at the end of the draw}}Copy the code

At this point, the completion of the simple function of the lottery component