Write an interesting elevator applet by hand

Time:2022-11-25

What will you learn from this example?

CSS related

  1. css positioning
  2. CSS flexbox layout
  3. CSS animation
  4. Usage of CSS Variables

js-related

  1. class encapsulation
  2. DOM operation and event operation
  3. Style and Class Operations
  4. Use of delay functions

example effect

Cut the nonsense, first look at the effect as shown in the figure:

Write an interesting elevator applet by hand

Analyze the composition of applets

  1. Elevator shaft (that is, where the elevator goes up or down)
  2. elevator
  3. Elevator door (divided into left and right doors)
  4. floor
    4.1 Number of floors
    4.2 Floor buttons (including up and down buttons)

what functions

  1. Click on the floor to drive the elevator up or down
  2. The elevator reaches the corresponding floor, and the left and right doors of the elevator open
  3. After the door is opened, the beauty inside comes out
  4. Reminder message: This beauty is coming out soon, please come and greet her quickly
  5. The button will have a click-selected effect

According to the above analysis, we can implement the elevator applet very well. Next, let’s enter the coding stage.

PS: The number of floors here is dynamically generated, but it is recommended not to set the value too large, and you can limit it in the code.

Preparation

Create an index.html file and initialize the basic structure of the HTML5 document as follows:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>elevator</title>
    <link rel="stylesheet" href="./styles/style.css">
</head>
<body>
    <! -- Write the code here -->
</body>
<script src="https://www.eveningwater.com/static/plugin/message.min.js"></script>
<script src="./scripts/index.js"></script>
<script>
    //Write code here
</script>
</html>

Create a styles directory and create a style.css file, the initialization code is as follows:

//color variable
:root {
    --elevatorBorderColor--: rgba(0,0,0.85);
    --elevatorBtnBgColor--: #fff;
    --elevatorBtnBgDisabledColor--: #898989;
    --elevatorBtnDisabledColor--: #c2c3c4;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

how to call

We encapsulate the function in an Elevator class, and the final call is as follows:

//6 represents the number of floors, it is not recommended to set too large
 new Elevator(6);

The initialization work is complete, let’s take a look at the specific implementation.

Special statement: We use the flexible box layout here, please pay attention to browser compatibility issues.

Code

build a building

First of all, we need a container element, which represents the current building or building containing the elevator. The HTML code is as follows:

<div class="ew-elevator-building"></div>

The style of the building is very simple, the width is fixed, and the minimum height is set (because the number of floors is dynamically generated and not fixed, so the height cannot be fixed), then the border is set, and there is nothing else. code show as below:

.ew-elevator-building {
    width: 350px;
    max-width: 100%;
    min-height: 500px;
    border: 6px solid var(--elevatorBorderColor--);
    margin: 3vh auto;
    overflow: hidden;
    display: flex;  
}

Construct the elevator shaft

Next is the elevator shaft, and the elevator shaft contains the elevator and the left and right doors of the elevator, so the HTML document structure comes out, as follows:

<! -- Elevator shaft -->
<div class="ew-elevator-shaft">
    <! -- Elevator -->
    <div class="ew-elevator">
        <! -- Elevator left and right doors -->
        <div class="ew-elevator-left-door ew-elevator-door"></div>
        <div class="ew-elevator-right-door ew-elevator-door"></div>
    </div>
</div>

According to the effect diagram, we can quickly write the style code, as shown below:

//Elevator shaft, mainly to set relative positioning and frame, fixed width
.ew-elevator-shaft {
    border-right: 2px solid var(--elevatorBorderColor--);
    width: 200px;
    padding: 1px;
    position: relative;
}

build elevator

Let’s think about why relative positioning is used here, because our elevator is rising and falling, we can simulate the rising and falling of the elevator by adding the offset of bottom or top by absolute positioning, so we need to set the elevator to absolute Positioning, and the elevator is offset relative to the elevator shaft, so it needs to be set to relative positioning. Continue to write style code:

.ew-elevator {
    height: 98px;
    width: calc(100% - 2px);
    background: url("https://www.eveningwater.com/my-web-projects/js/26/img/6.jpg") center / cover no-repeat;
    border: 1px solid var(--elevatorBorderColor--);
    padding: 1px;
    transition-timing-function: ease-in-out;
    position: absolute;
    left: 1px;
    bottom: 1px;
}

Build the elevator doors

It can be seen that the default elevator is on the first floor, so the bottom offset is 1px, and we set a background image to represent the people inside, and the background image will be displayed after the elevator door is opened. Next is the style of the elevator door:

.ew-elevator-door {
    position: absolute;
    width: 50%;
    height: 100%;
    background-color: var(--elevatorBorderColor--);
    border: 1px solid var(--elevatorBtnBgColor--);
    top: 0;
}

.ew-elevator-left-door {
    left: 0;
}

.ew-elevator-right-door {
   right: 0;
}

In fact, it is easy to understand, that is, the left and right doors each take one step, the elevator door on the left is on the left, and the elevator door on the right is on the right. The next step is to add animation to the elevator door opening, just add a toggle class name:

.ew-elevator-left-door.toggle {
    animation: doorLeft 3s 1s cubic-bezier(0.075, 0.82, 0.165, 1);
}

.ew-elevator-right-door.toggle {
    animation: doorRight 3s 1s cubic-bezier(0.075, 0.82, 0.165, 1);
}

@keyframes doorLeft {
    0% {
        left: 0px;
    }
    25% {
        left: -90px;
    }
    50% {
        left: -90px;
    }
    100% {
        left:0;
    }
}

@keyframes doorRight {
    0% {
        right: 0px;
    }
    25% {
        right: -90px;
    }
    50% {
        right: -90px;
    }
    100% {
        right:0;
    }
}

Obviously, the door on the left of the elevator is offset to the left, and the door on the right of the elevator is offset to the right.

build floor

Next is the style of the floor. The floor also includes two parts. The first is the elevator button control, and the second is the number of floors. So the HTML document structure is as follows:

<div class="ew-elevator-storey-zone">
    <! This block is the floor, because it is dynamically generated, but we can write the first one first, and then write the style -->
    <div class="ew-elevator-storey">
        <! -- Elevator button, including up button and down button -->
        <div class="ew-elevator-controller">
           <button type="button" class="ew-elevator-to-top ew-elevator-btn">↑</button>
           <button type="button" class="ew-elevator-to-bottom ew-elevator-btn">↓</button>
        </div>
        <! -- Number of floors -->
        <div class="ew-elevator-count">1</div>
    </div>
</div>

The style of the floor container and each floor element is very simple and there is nothing to say, as follows:

//floor container element
.ew-elevator-storey-zone {
    width: auto;
    height: 100%;
}
//floor element
.ew-elevator-storey {
    display: flex;
    align-items: center;
    height: 98px;
    border-bottom: 1px solid var(--elevatorBorderColor--);
}

Build the elevator button

Next is the container element for the elevator button, as follows:

.ew-elevator-controller {
    width: 70px;
    height: 98px;
    padding: 8px 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
}

They are all conventional styles, such as the vertical and horizontal centering of the flexible box, and the line break with the column. Even the button element has nothing to say.

// elevator button
.ew-elevator-btn {
    width: 36px;
    height: 36px;
    border: 1px solid var(--elevatorBorderColor--);
    border-radius: 50%;
    outline: none;
    cursor: pointer;
    background-color: var(--elevatorBtnBgColor--);
}
//Need to add a selected style effect to the button
.ew-elevator-btn.checked {
    background-color: var(--elevatorBorderColor--);
    color:var(--elevatorBtnBgColor--);
}
//add space
.ew-elevator-btn:last-of-type {
    margin-top: 8px;
}
// button disabled
.ew-elevator-btn[disabled] {
    cursor: not-allowed;
    background-color: var(--elevatorBtnBgDisabledColor--);
    color: var(--elevatorBtnDisabledColor--);
}
//floor number style
.ew-elevator-count {
    width: 80px;
    height: 98px;
    text-align: center;
    font: 56px / 98px "Microsoft Yahei", "Italics";
}

js code

initialization class

This is the end of the html document and CSS layout. The next step is the implementation of the functional logic. First, we define a class called Elevator and initialize some of its properties. The code is as follows:

class Elevator {
    // The parameter represents the number of floors
    constructor(count){
         //The total number of floors is cached
        this.count = count;
        //Current floor index
        this.onFloor = 1;
        //Elevator button group
        this.btnGroup = null;
        // Dynamically generate container elements for the number of floors
        this.zoneContainer = this.$(".ew-elevator-storey-zone");
        //Elevator element
        this.elevator = this.$(".ew-elevator");
    }
}

Add tool method to get DOM

After the initialization work is completed, we need to add some tool methods, such as obtaining DOM elements, we use the document.querySelector and document.querySelectorAll methods, we encapsulate these two methods, and then become the following code:

class Elevator {
    // The parameter represents the number of floors
    constructor(count){
         //The total number of floors is cached
        this.count = count;
        //Current floor index
        this.onFloor = 1;
        //Elevator button group
        this.btnGroup = null;
        // Dynamically generate container elements for the number of floors
        this.zoneContainer = this.$(".ew-elevator-storey-zone");
        //Elevator element
        this.elevator = this.$(".ew-elevator");
    }
    //Here is the encapsulated code
    $(selector, el = document) {
       return el.querySelector(selector);
    }
    $$(selector, el = document) {
      return el.querySelectorAll(selector);
    }
}

Dynamically generate the number of floors

Next, we need to dynamically generate the number of floors according to the parameters passed in, and call it in the constructor, so the code becomes as follows:

class Elevator {
    // The parameter represents the number of floors
    constructor(count){
         //The total number of floors is cached
        this.count = count;
        //Current floor index
        this.onFloor = 1;
        //Elevator button group
        this.btnGroup = null;
        // Dynamically generate container elements for the number of floors
        this.zoneContainer = this.$(".ew-elevator-storey-zone");
        //Elevator element
        this.elevator = this.$(".ew-elevator");
        //add code here
        this.generateStorey(this.count || 6);
    }
    //Here is the encapsulated code
    $(selector, el = document) {
       return el.querySelector(selector);
    }
    $$(selector, el = document) {
      return el.querySelectorAll(selector);
    }
    //add code here
    generateStorey(count){
       // write logic here
    }
}

Ok, let’s think about how we should generate DOM elements and add them to DOM elements. It’s very simple. We can use the splicing of template strings, and then add the spliced ​​templates to the innerHTML of the container element. as follows:

GenerateStorey method internal code:

    let template = "";
    for (let i = count - 1; i >= 0; i--) {
      template += `
                <div class="ew-elevator-storey">
                    <div class="ew-elevator-controller">
                        <button type="button" class="ew-elevator-to-top ew-elevator-btn" ${
                          i === count - 1 ? "disabled" : ""
                        }>↑</button>
                        <button type="button" class="ew-elevator-to-bottom ew-elevator-btn" ${
                          i === 0 ? "disabled" : ""
                        }>↓</button>
                    </div>
                    <div class="ew-elevator-count">${i + 1}</div>
                </div>
            `;
    }
    this.zoneContainer.innerHTML = template;
    this.storeys = this.$$(".ew-elevator-storey", this.zoneContainer);
    this.doors = this.$$(".ew-elevator-door", this.elevator);
    this.btnGroup = this.$$(".ew-elevator-btn", this.zoneContainer);

Here we need to pay attention that there is no ascending operation on the top floor, so the ascending button on the top floor needs to be disabled, and there is no descending operation on the bottom floor, so the descending button on the ground floor should also be disabled. This is also the meaning of the following code:

i === count - 1 ? "disabled" : "";
i === 0 ? "disabled" : "";

Add a click event to the button

We judge by the index, and then add the disabled attribute to the button to disable the button click. After the dynamic generation is complete, we are initializing the floor element array, the elevator door element array and the button element array. Then we just need to add a click event to the button, and continue to add the following line of code inside the generateStorey method:

 [...this.storeys].forEach((item, index) => {
      this.handleClick(
        this.$$(".ew-elevator-btn", item),
        item.offsetHeight,
        index
      );
 });

The above code is to get the elevator button of each floor, because the offset is related to the height of the floor, so we get the offsetHeight of each floor to determine the offset of the bottom, and then the index of each floor is used to calculate the offset displacement.

Continue to look at the handleClick method, as follows:

handleClick(btnGroup, floorHeight, floorIndex) {
    Array.from(btnGroup).forEach((btn) => {
      btn.addEventListener("click", () => {
        if (btn.classList.contains("checked")) {
          return;
        }
        btn.classList.add("checked");
        const currentFloor = this.count - floorIndex;
        const moveFloor = currentFloor - 1;
        this.elevatorMove(currentFloor, floorHeight * moveFloor);
      });
    });
}

Elevator ascent and descent

The inside of this method is actually to add a click event for each button. Clicking first determines whether it is selected. If it is selected, it will not be executed. Otherwise, add the selected style and then subtract the current clicked floor index from the total number of floors. Subtract 1 to get the current need to move number of floors, and then call the elevatorMove method, passing the current floor index and the offset of the movement as parameters to the method. Next we look at the elevatorMove method.

elevatorMove(num, offset) {
    const currentFloor = this.onFloor;
    const diffFloor = Math.abs(num - currentFloor);

    this.addStyles(this.elevator, {
      transitionDuration: diffFloor + "s",
      bottom: offset + "px",
    });

    Array.from(this.doors).forEach((door) => {
      door.classList.add("toggle");
      this.addStyles(door, {
        animationDelay: diffFloor + "s",
      });
    });

    $message.success(
      `This beauty is about to come out, please come and greet me quickly, and wait a little longer${
        (diffFloor * 1000 + 3000) / 1000
      }s close the elevator doors!`
    );

    setTimeout(() => {
      [...this.btnGroup].forEach((btn) => btn.classList.remove("checked"));
    }, diffFloor * 1000);

    setTimeout(() => {
      Array.from(this.doors).forEach((door) => door.classList.remove("toggle"));
    }, diffFloor * 1000 + 3000);

    this.onFloor = num;
}

Tool methods for adding styles

What operations have we done in this method? First, we use the floor index to calculate the execution transition time of the animation. This involves a tool method for adding styles. The code is as follows:

addStyles(el, styles) {
  Object.assign(el.style, styles);
}

Very simple, is to combine style and styles through Object.assign.

After adding the movement style of the elevator, we need to add a delay execution time for the opening of the elevator door andtoggleThe class name performs the animation of the elevator door opening, and then pops up a prompt messageThis beauty is about to come out, please hurry to greet her, wait for ${(diffFloor * 1000 + 3000) / 1000}s before closing the elevator door!, then delay the selection effect of the clear button, and finally delay the removal of the animation effect class name for opening the left and right doors of the elevatortoggle, and set the current floor, a simple elevator applet is completed.

full code

Finally, we merge the code, and the complete js code is completed:

class Elevator {
  constructor(count) {
    this.count = count;
    this.onFloor = 1;
    this.btnGroup = null;
    this.zoneContainer = this.$(".ew-elevator-storey-zone");
    this.elevator = this.$(".ew-elevator");
    this.generateStorey(this.count || 6);
  }
  $(selector, el = document) {
    return el.querySelector(selector);
  }
  $$(selector, el = document) {
    return el.querySelectorAll(selector);
  }
  generateStorey(count) {
    let template = "";
    for (let i = count - 1; i >= 0; i--) {
      template += `
                <div class="ew-elevator-storey">
                    <div class="ew-elevator-controller">
                        <button type="button" class="ew-elevator-to-top ew-elevator-btn" ${
                          i === count - 1 ? "disabled" : ""
                        }>↑</button>
                        <button type="button" class="ew-elevator-to-bottom ew-elevator-btn" ${
                          i === 0 ? "disabled" : ""
                        }>↓</button>
                    </div>
                    <div class="ew-elevator-count">${i + 1}</div>
                </div>
            `;
    }
    this.zoneContainer.innerHTML = template;
    this.storeys = this.$$(".ew-elevator-storey", this.zoneContainer);
    this.doors = this.$$(".ew-elevator-door", this.elevator);
    this.btnGroup = this.$$(".ew-elevator-btn", this.zoneContainer);
    [...this.storeys].forEach((item, index) => {
      this.handleClick(
        this.$$(".ew-elevator-btn", item),
        item.offsetHeight,
        index
      );
    });
  }
  handleClick(btnGroup, floorHeight, floorIndex) {
    Array.from(btnGroup).forEach((btn) => {
      btn.addEventListener("click", () => {
        if (btn.classList.contains("checked")) {
          return;
        }
        btn.classList.add("checked");
        const currentFloor = this.count - floorIndex;
        const moveFloor = currentFloor - 1;
        this.elevatorMove(currentFloor, floorHeight * moveFloor);
      });
    });
  }
  elevatorMove(num, offset) {
    const currentFloor = this.onFloor;
    const diffFloor = Math.abs(num - currentFloor);

    this.addStyles(this.elevator, {
      transitionDuration: diffFloor + "s",
      bottom: offset + "px",
    });

    Array.from(this.doors).forEach((door) => {
      door.classList.add("toggle");
      this.addStyles(door, {
        animationDelay: diffFloor + "s",
      });
    });

    $message.success(
      `This beauty is about to come out, please come and greet me quickly, and wait a little longer${
        (diffFloor * 1000 + 3000) / 1000
      }s close the elevator doors!`
    );

    setTimeout(() => {
      [...this.btnGroup].forEach((btn) => btn.classList.remove("checked"));
    }, diffFloor * 1000);

    setTimeout(() => {
      Array.from(this.doors).forEach((door) => door.classList.remove("toggle"));
    }, diffFloor * 1000 + 3000);

    this.onFloor = num;
  }
  addStyles(el, styles) {
    Object.assign(el.style, styles);
  }
}

We can then instantiate the class as follows:

new Elevator(6);

at last

Of course, we can also expand here, such as the limit on the number of floors, or add the animation effect of the beauty inside after the door is opened, if you are interested, you can refer tosource codeExpand yourself.

online example

Finally, thank you for watching. If you think this article is helpful to you, please don’t hesitate to like and bookmark, and please click star, hehe.

Special statement: This small example is only suitable for novices to learn, and it is fine for the big guys. This small program is very simple for the big guys.