10
头图

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 manipulation and event manipulation
  3. Manipulating styles and classes
  4. Use of delay function

Sample effect

Without further ado, let's see the effect as shown in the figure:

Analysis of the composition of the applet

  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 are the functions

  1. Click on the floor to drive the elevator up or down
  2. When the elevator arrives at the corresponding floor, the left and right doors of the elevator open
  3. After the door opened, the beautiful woman inside came out
  4. Reminder: This beauty is coming out soon, please come to meet me 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 us enter the coding stage.

PS: The number of floors here is dynamically generated, but it is recommended not to set the value too large, 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>
    <!--这里写代码-->
</body>
<script src="https://www.eveningwater.com/static/plugin/message.min.js"></script>
<script src="./scripts/index.js"></script>
<script>
    //这里写代码
</script>
</html>

Create a styles directory and create a style.css file with the following initialization code:

 //色彩变量
: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代表楼层数,不建议设置太大
 new Elevator(6);

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

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

Code

build a building

First, we need a container element, representing the current building or building that contains the elevator. The HTML code is as follows:

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

The style of the building is very simple, fix the width, and set the minimum height (because the number of floors is dynamically generated and not fixed, so the height cannot be fixed), then set the border, and 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;  
}

Build an elevator shaft

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

 <!--电梯井-->
<div class="ew-elevator-shaft">
    <!--电梯-->
    <div class="ew-elevator">
        <!--电梯左右门-->
        <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 renderings, we can quickly write the style code, as shown below:

 //电梯井,主要就是设置相对定位和边框,固定宽度
.ew-elevator-shaft {
    border-right: 2px solid var(--elevatorBorderColor--);
    width: 200px;
    padding: 1px;
    position: relative;
}

Build an elevator

Let's think about why we use relative positioning here, because our elevator is rising and falling, we can simulate the rise and fall of the elevator by adding the offset of bottom or top through 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. Go ahead and write the 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;
}

Building 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 also easy to understand, that is, the left and right doors occupy one step each, 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 side of the elevator is offset to the left, and the door on the right side of the elevator is offset to the right.

build floors

Next is the style of the floor. The floor also contains 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">
    <!--这一块就是楼层,因为是动态生成的,但是我们可以先写第一个,然后写好样式-->
    <div class="ew-elevator-storey">
        <!--电梯按钮,包含上升按钮和下降按钮-->
        <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>
        <!--楼层数-->
        <div class="ew-elevator-count">1</div>
    </div>
</div>

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

 //楼层容器元素
.ew-elevator-storey-zone {
    width: auto;
    height: 100%;
}
//楼层元素
.ew-elevator-storey {
    display: flex;
    align-items: center;
    height: 98px;
    border-bottom: 1px solid var(--elevatorBorderColor--);
}

Build elevator buttons

Next is the container element for the elevator button, which looks like this:

 .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 vertical and horizontal centering of flex boxes, and column wrapping. Even button elements have nothing to say.

 //电梯按钮
.ew-elevator-btn {
    width: 36px;
    height: 36px;
    border: 1px solid var(--elevatorBorderColor--);
    border-radius: 50%;
    outline: none;
    cursor: pointer;
    background-color: var(--elevatorBtnBgColor--);
}
//需要给按钮添加一个选中样式效果
.ew-elevator-btn.checked {
    background-color: var(--elevatorBorderColor--);
    color:var(--elevatorBtnBgColor--);
}
//加点上间距
.ew-elevator-btn:last-of-type {
    margin-top: 8px;
}
//按钮禁用
.ew-elevator-btn[disabled] {
    cursor: not-allowed;
    background-color: var(--elevatorBtnBgDisabledColor--);
    color: var(--elevatorBtnDisabledColor--);
}
//楼层数样式
.ew-elevator-count {
    width: 80px;
    height: 98px;
    text-align: center;
    font: 56px / 98px "微软雅黑","楷体";
}

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 {
    // 参数代表楼层数
    constructor(count){
         //总楼层数缓存下来
        this.count = count;
        //当前楼层索引
        this.onFloor = 1;
        //电梯按钮组
        this.btnGroup = null;
        //动态生成楼层数的容器元素
        this.zoneContainer = this.$(".ew-elevator-storey-zone");
        //电梯元素
        this.elevator = this.$(".ew-elevator");
    }
}

Add a tool method to get the DOM

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

 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");
    }
    //这里是封装的代码
    $(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 {
    // 参数代表楼层数
    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){
       //这里写逻辑
    }
}

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

The code inside the generateStorey method:

 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 note 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 ground floor, and the descending button on the ground floor should also be disabled, which is also the meaning of the following code:

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

Add click event to 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 completed, we initialize 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, we obtain 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 Shifted.

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 to each button. Click first to determine whether it is selected. If it is selected, it will not be executed. Otherwise, the selected style will be added, and the current clicked floor index will be subtracted from the total number of floors to obtain the current need to move. The number of floors, and then call the elevatorMove method, passing the current floor index and the offset of the move as parameters into 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(
      `本美女就要出来了,请速速来迎接,再等${
        (diffFloor * 1000 + 3000) / 1000
      }s就关电梯门了!`
    );

    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. Here is a tool method for adding styles. The code is as follows:

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

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

After adding the moving style of the elevator, we need to add the delay execution time and toggle class name for the opening of the elevator door to execute the animation of the elevator door opening, and then a prompt message 本美女就要出来了,请速速来迎接,再等${(diffFloor * 1000 + 3000) / 1000}s就关电梯门了! pop up, and then delay Clear the selection effect of the button, and finally remove the animation effect class name of opening the left and right doors of the elevator toggle , 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(
      `本美女就要出来了,请速速来迎接,再等${
        (diffFloor * 1000 + 3000) / 1000
      }s就关电梯门了!`
    );

    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);
  }
}

Then we can instantiate this class as follows:

 new Elevator(6);

at last

Of course, we can also expand here, such as the limit on the number of floors, and add the animation effect of the beauty inside after the door is opened. If you are interested, you can refer to the source code to expand by yourself.

Online example

Finally, thank you for watching. If you think this article is helpful to you, I hope you don't hesitate to like and subscribe, and click star with your little hands, hehe.

Special statement: This small example is only suitable for beginners to learn, even if it is a big guy, this small program is very simple for a big guy.

夕水
5.2k 声望5.7k 粉丝

问之以是非而观其志,穷之以辞辩而观其变,资之以计谋而观其识,告知以祸难而观其勇,醉之以酒而观其性,临之以利而观其廉,期之以事而观其信。