Lake rules ~ no map no truth, first map!

I front-end small rookie, the big guys light snort woo woo

Let’s share the code

React + Typescript is used in React + Typescript projects. Change to ordinary HTML and JS are almost the same.

use

<Slider cityList={cityList} row={2} step={2} />
Copy the code

Component code

import React, { Component, ComponentType } from 'react'
import { Icon } from 'antd'

import './index.scss'

interface IProps {
  cityList: Array<number>,
  row: number,
  step: number
}

interface IStates {
  cityContainerWidth: number,
  cityWrapWidth: number,
  cityWrapTranslateX: number
}

class Slider extends Component<IProps.IStates> {
  constructor(props: IProps) {
    super(props)
    this.state = {
      cityContainerWidth: 0.cityWrapWidth: 0.cityWrapTranslateX: 0
    }
  }

  componentDidMount(): void {
    const { cityList, row } = this.props
    const cityWrapWidth: number = cityList.length > 12 ? Math.ceil(cityList.length / row) * 220 : 1320
    const cityWrapDom: HTMLElement | null = document.getElementById('city__wrap') as HTMLElement
    const cityContainerDom: HTMLElement | null = document.getElementById('city__container') as HTMLElement
    const cityContainerWidth: number = cityContainerDom.offsetWidth
    cityWrapDom && (cityWrapDom.style.width = `${cityWrapWidth}px`)

    this.setState({
      cityContainerWidth,
      cityWrapWidth
    })
  }

  handleArrowClick(direction: string): void {
    const { step } = this.props
    const { cityContainerWidth, cityWrapWidth, cityWrapTranslateX } = this.state
    const cityWrapDom: HTMLElement | null = document.getElementById('city__wrap') as HTMLElement
    * / / * step length
    const translateStep: number = 220 * step
    const translateDistance: number = translateStep * (direction === 'left' ? 1 : - 1)

    let newTranslateX: number = cityWrapTranslateX
    /* Relative distance moved */
    const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
    const isLeftEnd: boolean = relativeTranslateX <= cityContainerWidth
    const isLeftOverflow: boolean = relativeTranslateX - translateDistance <= cityContainerWidth
    const isRightEnd: boolean = relativeTranslateX + 10 >= cityWrapWidth // This 10 represents the 10 pixels of the right margin, plus 10 hidden
    const isRightOverflow: boolean = relativeTranslateX - translateDistance >= cityWrapWidth

    /* Click the left arrow */
    if (translateDistance > 0) {
      /* Whether to reach the left end */
      if (isLeftEnd) return

      if (isLeftOverflow) {
        /* Out of range, slide just to the left end of the distance */
        newTranslateX = 0
      } else {
        /* The sliding distance is directly added to the step size */
        newTranslateX += translateDistance
      }

    } else if (translateDistance < 0) {
      /* Does it reach the right end */
      if (isRightEnd) return

      if (isRightOverflow) {
        /* Out of range, slide just to the end of the right distance */
        newTranslateX += relativeTranslateX + 10 - cityWrapWidth
      } else {
        /* The sliding distance is directly added to the step size */
        newTranslateX += translateDistance
      }
    }

    const transformString: string = `translateX(${newTranslateX}px)`
    cityWrapDom && (cityWrapDom.style.transform = transformString)
    this.setState({
      cityWrapTranslateX: newTranslateX
    })
  }

  render() {
    const { cityList } = this.props
    return(<div className="city" > <div className="city__title"> I am a wheel map </div> <div className="city__container" id="city__container"> <div className="city__arrow" onClick={() => this.handleArrowClick('left')}> <Icon className="icon"  type="left" /> </div> <div className="city__arrow city__arrow--right" onClick={() => this.handleArrowClick('right')}> <Icon className="icon" type="right" /> </div> <div className="city__wrap" id="city__wrap"> {cityList.map(item => ( <div className="city__item" key={item}>{item}</div> ))} </div> </div> </div> ) } } export default Slider as ComponentType<IProps>Copy the code

Style files (using SASS for style writing)

.city {
  &__container {
    position: relative;
    overflow: hidden;
    width: 1200px;
    padding-top: 10px;

    &::-webkit-scrollbar {
      width: 15px;
      height: 15px;
    }

    &::-webkit-scrollbar-track {
      border-radius: 20px;
      background: #e7e7e7;
    }

    &::-webkit-scrollbar-thumb {
      background: #66a6ff;
      background-image: linear-gradient(120deg.#89a4fe 0%.#66a6ff 100%);
      border-radius: 20px;
    }
  }

  &__title {
    margin-top: 30px;
    color: # 333;
    font-size: 24px;
    font-weight: bold;
  }

  &__arrow {
    position: absolute;
    display: flex;
    justify-content: center;
    align-items: center;
    top: 50%;
    width: 50px;
    height: 100px;
    background: rgba(0.0.0.0.7);
    transform: translateY(-50%);
    transition: all .3s ease;
    z-index: 2;
    opacity:.5;
    cursor: pointer;

    &--right {
      right: 0;
    }

    .icon {
      color: #fff;
      font-size: 30px;
    }

    &:hover{
      opacity: 1;
    }
  }

  &__wrap {
    transition: all .3s ease-in-out;
  }

  &__item {
    float: left;
    width: 210px;
    height: 90px;
    margin: 0 10px 10px 0;
    color: #fff;
    font-size: 40px;
    font-weight: bold;
    line-height: 90px;
    text-align: center;
    // background: url(https://static.zhipin.com/zhipin-geek/v98/web/geek/images/city_101010100.png) no-repeat;
    // background-size: cover;
    background-image: linear-gradient(135deg.#667eea 0%.#764ba2 100%); }}Copy the code

And then the last one comes up hereitemList

This is a list of data that will be used for presentation. Here we can use the function to generate a list for presentation, which you can replace with a list of access interfaces if necessary…

  getCityList(): Promise<Array<number>> {
    return new Promise(async (resolve, reject) => {
      const length: number = 15
      const cityList: Array<number> = Array.from({ length }, (_: unknown, index: number): number= > index + 1)
      resolve(cityList)
    })
  }
Copy the code

We are here to generate a length of 15 natural number array,2,3,4,5,6,7,8,9,10,11,12,13,14,15 [1]

The code analysis

Although there are comments in the code, I would like to share them briefly

The steps, of course, are: get the list of data to display -> pass the list to the slider -> accept it and set the dynamic width of the container based on the length of the list and the number of rows to display

In the next is the highlight!!

I did a lot of work on how the container slides, so let’s take a look at it a little bit, shall we

  1. Listen for the function that clicks the arrow button

    handleArrowClick(direction)And what this function means is, when you click the left arrow, direction isleftWhereas this isright.
  <div className="city__arrow" onClick={() => this.handleArrowClick('left')}>
    <Icon className="icon" type="left" />
  </div>
  <div className="city__arrow city__arrow--right" onClick={() => this.handleArrowClick('right')}>
    <Icon className="icon" type="right" />
  </div>
Copy the code

These are the left and right arrows, and clicking on the left arrow passes the function a string representing the direction

  1. Define the step length
/* step length */ const translateStep: number = 220 * step const translateDistance: number = translateStep * (direction ==='left'? 1:1)Copy the code

This is simply based on the size of the step passed in by the parent (how many elements slide per click) and the direction passed in when the button is clicked. If it is left, the distance moved is -step, and here we use transform: The positive axis is to the right, so if you want to move the content to the right, you need to set the distance of movement to a negative number, so that relative movement can achieve the perfect effect. It’s ok if you don’t understand it now, let’s continue to look at it later.

  1. Get the moving distance, and judge whether it is beyond the point!! Highlight!! Highlight!!

So the first thing we’re going to do is we’re going to set state, and we’re going to define a variable that says how far we’ve slid, and we’re going to start with 0

State = {cityWrapTranslateX: 0}Copy the code

Then we get the sliding component of the outer container with the width of the container, if you do not understand the words you can baidu the implementation principle of the wheel map, a simple explanation of that is to say

Outside the container is visible part, and the inner container is one part will be hidden, and then according to the size of the outer container, can display the content of the container inside, is the overlapping part of the figure, imagine ~ in container at the time of moving around, we see things in the outside of the container is also in w move the ~

Good, do not understand can go to Baidu to look for more detailed round cast diagram principle! Let me continue my analysis

Const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateXCopy the code

And then we’re going to get the size of the coming container cityContainerWidth and the distance that we’ve slid cityWrapTranslateX and we’re going to get the relative distance that we’ve moved by subtracting the two. What does that mean? Let’s go ahead and talk about all the boundary conditions and you’ll get the idea!

const isLeftEnd: boolean = relativeTranslateX <= cityContainerWidth const isLeftOverflow: boolean = relativeTranslateX - translateDistance <= cityContainerWidth const isRightEnd: Boolean = relativeTranslateX + 10 >= cityWrapWidth // This is 10 pixels to the right, plus 10 to hide const isRightOverflow: boolean = relativeTranslateX - translateDistance >= cityWrapWidthCopy the code

The four variables are: Have you reached the left end (can’t click anymore!) , whether the left side of the button will overflow (it’s too much sliding!) , whether it reaches the end of the right side, whether the right side of the button will overflow

Let’s just focus on the latter two, which are pretty much the same (because I like the right side). Let’s assume that the width of the outer container is 1200, and the width of the inner container is 2000

  • isRightEnd
Const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX const isRightEnd: Boolean = relativeTranslateX + 10 >= cityWrapWidth // This 10 is representing 10 pixels of the right margin, plus 10 hiddenCopy the code

We’re going to ignore the 10, because each element has a margin of 10px, so we’re going to add, we can ignore it for now

Next, if we move 800px to the right, cityWrapTranslateX is -800. So we’re going to move 1,200 minus minus 800 relative to each other, which is 2,000, just like the inside container

Const isRightEnd: Boolean = relativeTranslateX + 10 >= cityWrapWidth const isRightEnd: Boolean = relativeTranslateX + 10 >= cityWrapWidth const isRightEnd: Boolean = relativeTranslateX + 10 >= cityWrapWidth

  • IsRightOverflow isRightOverflow isRightOverflow isRightOverflow isRightOverflow isRightOverflow isRightOverflow isRightOverflow isRightOverflow isRightOverflow isRightOverflow

There is a lot of white space on the right side due to excessive sliding

So let’s determine if the relative movement is beyond the width of the inside container, that is, beyond the width of the display

const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
const isRightOverflow: boolean = relativeTranslateX - translateDistance >= cityWrapWidth
Copy the code

Let’s look at the picture below

That’s where we are now, and we only have 300px left to go to the end, but when we set each slide to 600px, we’re going to move 1200 – (-500) which is 1700 relative to each other, RelativeTranslateX – translateDistance = 1700 – (-600)

We now define a variable newTranslateX to represent the new slide distance. The default is the distance of the last slide

let newTranslateX: number = cityWrapTranslateX
Copy the code

If it is going to overflow, let’s set it to the previous sliding distance plus the relative sliding distance minus the width of the container, so it might not make sense, so let’s calculate it

NewTranslateX = 500 (already slid 500) relativeTranslateX = 1200 cityWrapWidth = 2000

NewTranslateX = 500 + 1200-2000 = -300 And that’s just enough to get to the end!

newTranslateX += relativeTranslateX - cityWrapWidth
Copy the code

Finally, we can set the latest sliding distance according to various situations. After setting the sliding distance, we can change the style

/* Click the left arrow */if(translateDistance > 0) {/* Does it reach the left end */if (isLeftEnd) return

  if(isLeftOverflow) {/* Out of range, slide just to the left end distance */ newTranslateX = 0}else*/ newTranslateX += translateDistance}}else if(translateDistance < 0) {/* Does it reach the right end */if (isRightEnd) return

  if(isRightOverflow) {/* Out of range, slide right to the end of the right */ newTranslateX += relativeTranslateX + 10 - cityWrapWidth}else{/* < translateX += translateDistance}} const transformString: string = 'translateX(${newTranslateX}px)`
cityWrapDom && (cityWrapDom.style.transform = transformString)
this.setState({
  cityWrapTranslateX: newTranslateX
})
Copy the code
  1. We divide the data based on the number of rows that are passed in
const { cityList, row } = this.props
const cityWrapWidth: number = cityList.length > 12 ? Math.ceil(cityList.length / row) * 220 : 1320
const cityWrapDom: HTMLElement | null = document.getElementById('city__wrap') as HTMLElement
cityWrapDom && (cityWrapDom.style.width = `${cityWrapWidth}px`)
Copy the code

It’s just a simple matter of how many elements there are, so by default, we show 12, two rows, six in a row, and if there are less than 12, we show them separately

Set to three lines:

Feel the whole article too much text, very wordy, but also a small summary, chicken writing out or a little excited, you can take out a pen and paper on the paper to draw a sketch, soon you can draw the relationship, and then slowly pushed out!

Put a link to an article about a small program you wrote earlier