使用Vanilla JavaScript模块处理CSS过渡状态

作者 : IT 大叔 本文共5187个字,预计阅读时间需要13分钟 发布时间: 2020-09-1

在很多前端工作中,我最终不得不为组件创建打开和关闭转换。诸如模式,抽屉,下拉菜单等之类的东西。做了几次之后,我开始注意到一个模式,并且想知道是否可以将该模式变成可重用的模块,而不是重写相同逻辑的变体。这些是我的核心要求:

  1. 过渡应该由CSS而非JavaScript处理。这意味着,如果一个组件的过渡持续时间与另一个组件的过渡持续时间不同,则JS应该在两种情况下都可以正常工作。
  2. 应该有能力关闭转换并仅在两个最终状态之间切换。
  3. 过渡应该防止垃圾邮件,这意味着如果组件当前处于“打开”状态,我不想触发“关闭”过渡。
  4. 返回承诺,这样我们就可以编写在过渡完成后发生的代码。

让我们从选项开始

首先,我想创建一个可以存储设置的选项对象。我们将要在这里定义状态类,以及是否启用转换:

const options = {
  stateOpened: "is-opened",
  stateOpening: "is-opening",
  stateClosed: "is-closed",
  stateClosing: "is-closing",
  transition: true
};

创建我们的过渡模块

接下来,让我们创建一个名为的新文件,transition.js并定义一个返回诺言的“打开”函数

const openTransition = (el, settings) => {
  return new Promise((resolve) => {
    resolve(el);
  });
};

目前,它并没有做太多事情,我们只是在兑现承诺并返回我们通过的元素。因此,让我们首先汇总一下我们的过渡条件。

const openTransition = (el, settings) => {
  return new Promise((resolve) => {
    if (settings.transition) {
      // Lets transition the element...
    } else {
      // Transition is disabled, just swap the state
    }
  });
};

对于禁用的逻辑,这非常简单,我们只需要删除关闭状态并添加打开状态即可。然后我们可以兑现诺言。

el.classList.add(settings.stateClosed);
el.classList.remove(settings.stateOpened);
resolve(el);

现在,如果启用了转换,我们要执行以下操作:

  1. 通过设置过渡类并监听事件,开始从状态a-> b过渡transitionend
  2. 一旦transitionend被击中,我们可以然后交换我们的过渡类为我们的最终状态和解决的承诺。
el.classList.remove(settings.stateClosed);
el.classList.add(settings.stateOpening);
el.addEventListener('transitionend', function _f() {
  el.classList.add(settings.stateOpened);
  el.classList.remove(settings.stateOpening);
  resolve(el);
  this.removeEventListener('transitionend', _f);
});

有趣的是,我们仅在过渡期间添加事件监听器。过渡完成后,我们可以将其删除,直到进行另一个过渡调用为止。

openTransition现在的最终代码如下:

const openTransition = (el, settings) => {
  return new Promise((resolve) => {
    if (settings.transition) {
      el.classList.remove(settings.stateClosed);
      el.classList.add(settings.stateOpening);
      el.addEventListener('transitionend', function _f() {
        el.classList.add(settings.stateOpened);
        el.classList.remove(settings.stateOpening);
        resolve(el);
        this.removeEventListener('transitionend', _f);
      });
    } else {
      el.classList.add(settings.stateOpened);
      el.classList.remove(settings.stateClosed);
      resolve(el);
    }
  });
};

完成后,我们可以closeTransition轻松地将推论功能组合在一起,只需移动要添加和删除的类,就像这样:

const closeTransition = (el, settings) => {
  return new Promise((resolve) => {
    if (settings.transition) {
      el.classList.add(settings.stateClosing);
      el.classList.remove(settings.stateOpened);
      el.addEventListener('transitionend', function _f() {
        el.classList.remove(settings.stateClosing);
        el.classList.add(settings.stateClosed);
        resolve(el);
        this.removeEventListener('transitionend', _f);
      });
    } else {
      el.classList.add(settings.stateClosed);
      el.classList.remove(settings.stateOpened);
      resolve(el);
    }
  });
};

要将这两个功能转换为模块,我们只需要导出它们,就像这样:

// transition.js
export const openTransition = (el, settings) => {
  // ...
};

export const closeTransition = (el, settings) => {
  // ...
};

添加我们的标记和触发逻辑

让我们从一个人为设计的示例开始,仅说明这些转换函数的灵活性。让我们创建一个index.html有按钮的文件,然后在两个状态之间转换一些元素。

<!-- index.html -->
<button class="button">Trigger</button>
<div class="box-track">
  <div class="box is-closed"></div>
</div>

请务必注意,在这种情况下,我们将直接添加组件的默认状态is-closed。如果您想打开默认状态,只需添加即可is-opened

现在,让我们创建一个index.js文件,在该文件中导入新的过渡模块,定义选项并准备使用我们的两个元素。

// index.js
import {
  openTransition,
  closeTransition
} from "./transition";

const options = {
  stateOpened: "is-opened",
  stateOpening: "is-opening",
  stateClosed: "is-closed",
  stateClosing: "is-closing",
  transition: true
};

const el = document.querySelector(".box");
const btn = document.querySelector(".button");

接下来,让click我们向按钮添加一个均匀的侦听器。请注意,这是我们将检查组件是否完成转换的地方。如果我们的组件未处于“最终”状态(例如is-opened或),我们将不执行任何操作is-closed

btn.addEventListener("click", () => {
  if (el.classList.contains(options.stateClosed)) {
    // ...
  } else if (el.classList.contains(options.stateOpened)) {
    // ...
  }
});

现在,我们要做的就是使用导入的转换模块,并在关闭时打开组件,或者在打开时关闭组件。我们将异步编写此代码,以利用我们返回的承诺。

btn.addEventListener("click", async () => {
  if (el.classList.contains(options.stateClosed)) {
    await openTransition(el, options);
    // Do stuff after open transition has finished...
  } else if (el.classList.contains(options.stateOpened)) {
    await closeTransition(el, options);
    // Do stuff after close transition has finished...
  }
});

这就是我们的JavaScript!决赛index.js现在应该像这样:

// index.js
import {
  openTransition,
  closeTransition
} from "@vrembem/core/src/js/transition";

const options = {
  stateOpened: "is-opened",
  stateOpening: "is-opening",
  stateClosed: "is-closed",
  stateClosing: "is-closing",
  transition: true
};

const el = document.querySelector(".box");
const btn = document.querySelector(".button");

btn.addEventListener("click", async () => {
  if (el.classList.contains(options.stateClosed)) {
    await openTransition(el, options);
  } else if (el.classList.contains(options.stateOpened)) {
    await closeTransition(el, options);
  }
});

添加我们的CSS过渡

示例的最后一部分是将CSS过渡添加到组件中。这就是所有这些的好处,我们基本上可以编写具有任何过渡持续时间的任何过渡,并且我们的JavaScript应该可以很好地处理它。

为简单起见,我们仅在背景色和变换属性之间进行转换,因此它不是真正的“打开”和“关闭”状态,但是它显示了使用最小样式的可能性。这是我们的基本样式:

.box-track {
  position: relative;
}

.box {
  position: absolute;
  width: 50%;
  height: 6em;
  border-radius: 8px;
}

现在,让我们介绍我们的州风格。这将是我们最终状态将具有的样式:

.box.is-opened,
.box.is-opening {
  background: salmon;
  transform: translateX(100%);
}

.box.is-closed,
.box.is-closing {
  background: forestgreen;
  transform: translateX(0);
}

最后,让我们仅将过渡样式添加到关心过渡的状态中,is-openingis-closing

.box.is-opening,
.box.is-closing {
  transition-property: background, transform;
  transition-duration: 1s;
  transition-timing-function: ease-in-out;
}

结论

将所有这些放在一起,我们现在有了一个可重用的过渡模块,该模块可在多个组件之间使用。我们的过渡本身完全由CSS处理,我们可以根据需要使用不同的过渡类型将其添加到过渡模块中。

这是一些资源以及使用上述过渡模块的两个组件:

免责声明:
1. 本站资源转自互联网,源码资源分享仅供交流学习,下载后切勿用于商业用途,否则开发者追究责任与本站无关!
2. 本站使用「署名 4.0 国际」创作协议,可自由转载、引用,但需署名原版权作者且注明文章出处
3. 未登录无法下载,登录使用金币下载所有资源。
IT小站 » 使用Vanilla JavaScript模块处理CSS过渡状态

常见问题FAQ

没有金币/金币不足 怎么办?
本站已开通每日签到送金币,每日签到赠送五枚金币,金币可累积。
所有资源普通会员都能下载吗?
本站所有资源普通会员都可以下载,需要消耗金币下载的白金会员资源,通过每日签到,即可获取免费金币,金币可累积使用。

发表评论