乐趣区

关于javascript:手写一个有点意思的电梯小程序

通过本示例,你将学到什么?

CSS 相干

  1. CSS 定位
  2. CSS 弹性盒子布局
  3. CSS 动画
  4. CSS 变量的用法

js 相干

  1. 类的封装
  2. DOM 的操作与事件的操作
  3. 款式与类的操作
  4. 提早函数的应用

示例成果

废话少说,先看成果如图所示:

剖析小程序的形成

  1. 电梯井(也就是电梯回升或者降落的中央)
  2. 电梯
  3. 电梯门(分为左右门)
  4. 楼层
    4.1 楼层数
    4.2 楼层按钮(蕴含回升和降落按钮)

有哪些性能

  1. 点击楼层,催动电梯回升或者降落
  2. 电梯达到对应楼层,电梯左右门关上
  3. 门关上之后,外面的美女就进去啦
  4. 提示信息: 本美女就要进去了,请速速来迎接
  5. 按钮会有一个点击选中的成果

依据以上的剖析,咱们就能够很好的实现电梯小程序啦,接下来让咱们进入编码阶段吧。

PS: 这里的楼层数是动静生层的,不过倡议值不要设置太大,能够在代码里做限度。

筹备工作

创立一个 index.html 文件,并初始化 HTML5 文档的根本构造,如下所示:

<!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>

创立一个 styles 目录并创立一个 style.css 文件,初始化代码如下:

// 色调变量
:root {--elevatorBorderColor--: rgba(0,0,0.85);
    --elevatorBtnBgColor--: #fff;
    --elevatorBtnBgDisabledColor--: #898989;
    --elevatorBtnDisabledColor--: #c2c3c4;
}

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

如何调用

咱们是通过将性能封装在一个 Elevator 类当中,最终调用如下所示:

// 6 代表楼层数,不倡议设置太大
 new Elevator(6);

初始化工作实现,接下来让咱们来看看具体的实现吧。

特地申明一下: 咱们在这里应用了弹性盒子布局, 请留神浏览器的兼容性问题。

代码实现

构建楼房

首先咱们须要一个容器元素,代表是以后的蕴含电梯的楼房或者是修建,HTML 代码如下:

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

修建的款式很简略,固定宽,并设置最小高度(因为楼层数是动静生成的,不固定的,所以高度不可能固定),而后设置边框,其它就没有什么了。代码如下:

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

构建电梯井

接下来是电梯井,电梯井又蕴含电梯和电梯左右门,因而 HTML 文档构造就进去了,如下所示:

<!-- 电梯井 -->
<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>

依据效果图,咱们能够很疾速的写出款式代码,如下所示:

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

构建电梯

咱们来思考一下这里为什么要应用绝对定位,因为咱们的电梯是回升和降落的,咱们能够通过相对定位加 bottom 或者 top 的偏移量来模仿电梯的回升和降落,因而电梯咱们就须要设置为相对定位,而电梯是绝对于电梯井偏移的,所以须要设置为绝对定位。持续编写款式代码:

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

构建电梯门

能够看到,默认电梯就在第一层,所以 bottom 偏移量就是 1px, 并且咱们设置一个背景图,代表是外面的人,电梯门开启后就显示该背景图。接下来是电梯门的款式:

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

其实也很好了解,就是左右门各占一步,右边的电梯门居左,左边的电梯门居右。接下来是给电梯开门增加动画, 增加一个 toggle 类名就行了:

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

很显然电梯右边的门是往左边偏移,电梯左边的门是往右边偏移。

构建楼层

接下来是楼层的款式,楼层也蕴含了两个局部,第一个就是电梯按钮管制,第二个就是楼层数。因而 HTML 文档构造如下:

<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>

楼层容器和每个楼层元素的款式很简略没什么能够说的,如下:

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

构建电梯按钮

接下来是电梯按钮的容器元素,如下所示:

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

都是惯例的款式,比方弹性盒子的垂直程度居中,与列换行,即使是按钮元素也没有什么好说的。

// 电梯按钮
.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 代码

初始化类

html 文档和 CSS 布局就到此为止了,接下来才是重头戏,也就是性能逻辑的实现,首先咱们定义一个类叫 Elevator,并初始化它的一些属性,代码如下:

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

增加获取 DOM 的工具办法

初始化工作实现后,咱们须要增加一些工具办法,例如获取 DOM 元素,咱们采纳 document.querySelector 与 document.querySelectorAll 办法,咱们将这两个办法封装一下,于是变成如下的代码:

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

动静生成楼层数

接下来,咱们要依据传入的参数动静生成楼层数,在构造函数中调用它,因而代码就变成了如下:

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){// 这里写逻辑}
}

好了,让咱们思考一下,咱们应该如何生成 DOM 元素并增加到 DOM 元素中,很简略,咱们能够利用模板字符串的拼接,而后将拼接好的模板增加到容器元素的 innerHTML 中。如下:

generateStorey 办法外部代码:

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

这里咱们须要留神一点,在顶楼是没有回升的操作,因而顶楼的回升按钮须要禁用掉,而底层也没有降落的操作,底楼的降落按钮也要禁用掉,这也是以下代码的意义所在:

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

为按钮增加点击事件

咱们通过索引来判断,而后给按钮增加 disabled 属性禁用按钮点击。动静生成实现之后,咱们在初始化楼层元素数组和电梯门的元素数组以及按钮元素数组。而后咱们就是须要给按钮增加点击事件,持续在 generateStorey 办法外部增加如下一行代码:

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

以上代码就是获取每一层楼的电梯按钮,因为偏移量与楼层的高度无关,所以咱们获取每个楼层的 offsetHeight 来确定 bottom 的偏移量,而后就是每层楼的索引,用来计算偏移量的。

持续看 handleClick 办法,如下:

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

电梯的回升与降落

该办法外部实际上就是为每个按钮增加点击事件,点击首先判断是否被选中,如果选中则不执行,否则增加选中款式而后通过楼层总数减去以后点击的楼层索引在减去 1 失去以后须要挪动的楼层数,而后调用 elevatorMove 办法,将以后楼层索引和挪动的偏移量当做参数传入该办法。接下来咱们来看 elevatorMove 办法。

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

非常简略,就是通过 Object.assign 将 style 和 styles 合并在一起。

增加了电梯的挪动款式,咱们就须要为电梯门的开启增加提早执行工夫以及 toggle 类名执行电梯门开启的动画,而后弹出提醒音讯 本美女就要进去了,请速速来迎接, 再等 ${(diffFloor * 1000 + 3000) / 1000}s 就关电梯门了!, 而后提早革除按钮的选中成果,最初提早移除开启电梯左右门的动画成果类名toggle, 并将以后楼层设置一下,一个简略的电梯小程序就实现了。

残缺代码

最初咱们合并一下代码,残缺的 js 代码就实现了:

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

而后咱们就能够实例化这个类了,如下所示:

new Elevator(6);

最初

当然这里咱们还能够扩大,比方楼层数的限度,再比方增加门开启后,外面的美女真的走进去的动画成果,如有趣味能够参考源码自行扩大。

在线示例

最初谢谢大家观看,如果感觉本文有帮忙到你,望不悭吝点赞和珍藏,动动小手点 star, 嘿嘿。

特地申明: 这个小示例只适宜老手学习,大佬就算了,这个小程序对大佬来说很简略。

退出移动版