使

使用 CSS 和 JavaScript 制作的 3D 键盘 HTML CSS JSDoc JavaScript 结论 更新

2025-06-10

使用 CSS 和 JavaScript 制作的 3D 键盘

HTML

CSS

JS文档

JavaScript

结论

更新

给我买杯咖啡

在这篇文章中,我将分享一个创建键盘的示例。

在创建键盘的过程中,我们将仔细研究 CSS 变量、JSDoc 符号和一些 JavaScript。

CSS 变量允许在布局定义中定义、修改和使用值。

过去几年,我每天都在使用 TypeScript,如果没有静态类型的安全性,我很难开发代码。不过,JSDoc 可以作为替代方案。虽然它不是一个类型,而是一个提示,但在纯 JavaScript 项目中还是值得尝试一下的。

好吧,JavaScript 就是 JavaScript!那么,我们开始吧!

演示文件可在此 gist 中找到

HTML

布局尽可能简单。

首先,让我们包含 CSS 定义(摘录):

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" type="text/css" href="keyboard.css" />
  </head>
</html>

接下来,让我们定义容器并包含脚本(摘录):

<!DOCTYPE html>
<html lang="en">
  <body>
    <div id="container"></div>
    <script src="keyboard.js"></script>
  </body>
</html>

最后,它看起来会像这样:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Keyboard</title>
    <link rel="stylesheet" type="text/css" href="keyboard.css" />
  </head>
  <body>
    <div id="container"></div>
    <script src="keyboard.js"></script>
  </body>
</html>

CSS

核心设置

描述键盘的基本设置定义如下:

:root {
  /* keyboard width */
  --width: 1000px;
  /* keyboard height */
  --height: 420px;
  /* border radius */
  --radius: 5px;
  /* defines how high the button is raised */
  --depth: 5px;
  /* letter color */
  --color: yellow;
}

它很有用,因为通过修改这些参数中的任何一个,您都可以配置整个键盘。

例如,通过重新定义--color变量,您可以设置字母的颜色以及发光按钮的强调色。

看法

必须将相应的规则应用于容器:

#container {
  /* the perspective is equal to the initial keyboard width */
  perspective: var(--width);
}

请注意,我们已经开始使用 CSS 变量。

键盘

键盘是一个 3D 旋转平面,其目标是分配由 JavaScript 中的数据和 CSS 样式定义的部分。

.keyboard {
  /* spreading sections evenly */
  display: flex;
  justify-content: space-between;
  /* setting the size */
  width: var(--width);
  height: var(--height);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  /* adding a gradient background */
  background-image: linear-gradient(to bottom, hsl(192 11% 53%) 0%, hsl(192 26% 43%) 100%);
  /* setting the border radius */
  border-radius: var(--radius);
  /* calculating paddings */
  padding: calc(var(--radius) * 2);
  box-sizing: border-box;
  /* enabling the 3d mode */
  transform-style: preserve-3d;
  /* applying the transform rule */
  transform: rotateX(0.13turn) rotateY(0turn) rotateZ(0turn);
}

这个类中的 width 和 height 是全局变量,border-radius 也是。但是 padding 是计算出来的,它取决于--radius变量:

calc(var(--radius) * 2)

它也是定义一些底层元素的 CSS 规则的好地方,例如 font-family 和全局边距。

覆盖

为了让键盘看起来更漂亮一些,让我们添加一个覆盖层。

.overlay {
  /* setting the size */
  width: var(--width);
  height: var(--height);
  /* centering the overlay */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translateX(-50%) translateY(-50%) translateZ(10px);
  /* adding a gradient background */
  background-image: linear-gradient(to bottom, #ffffff33 0%, transparent 100%);
  /* adding a noisy effect */
  filter: blur(25px);
}

叠加层将在键盘顶部添加发光效果。以下 CSS 规则用于创建此效果:

/* adding a gradient background */
background-image: linear-gradient(to bottom, #ffffff33 0%, transparent 100%);
/* adding a noisy effect */
filter: blur(25px);

部分

部分元素的主要目的是均匀分布行,这些行稍后将由 JavaScript 创建。

.section {
  /* spreading rows evenly */
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

这个元素是建议解决方案的弱点,尽管你可以通过修改按钮的 CSS 规则来调整部分的宽度。另一种方法是更改​​每行按钮的数量。

该行的设计是为了均匀分布按钮。

.row {
  /* spreading buttons evenly */
  display: flex;
  justify-content: space-between;
}

此外,行元素可用于调整按钮规则:

.row.functions .button {
  /* calculating the height of the function button */
  height: calc(var(--height) / 10);
}

按钮

最后,按钮。

大部分神奇的事情都发生在按钮上。请看下面的规则:

.button {
  /* setting the default dimensions of the button */
  --size: calc(var(--width) / 20);
  height: calc(var(--height) / 7);
  width: var(--size);
  /* setting the border radius */
  border-radius: var(--radius);
  /* centering the content of the button */
  display: flex;
  justify-content: center;
  align-items: center;
  /* additional settings */
  box-sizing: border-box;
  background: #000000;
  /* applying the global color */
  color: var(--color);
  /* adding the default margin */
  margin-left: calc(var(--width) / 200);
  /* raising the button above the keyboard */
  transform: translate3d(0px, 0px, var(--depth));
  /* enabling the 3d mode */
  transform-style: preserve-3d;
  /* calculating the perspective from the width */
  perspective: calc(var(--size) * 3);
}

注意,这里我们复用了 CSS 变量。首先,我们计算--size变量(它将被设置为按钮的宽度)。接下来,我们计算透视,而透视又取决于--size变量!

以下是摘录:

.button {
  /* setting the default dimensions of the button */
  --size: calc(var(--width) / 20);
  /* calculating the perspective from the width */
  perspective: calc(var(--size) * 3);
}

稍后,按钮特殊类的定义中会修改--size变量。如下所示:

.button.space {
  --size: calc(var(--width) / 2.3);
}

默认情况下,所有按钮都有左边距:

.button {
  /* adding the default margin */
  margin-left: calc(var(--width) / 200);
}

因此,为了让键盘看起来美观,我们必须禁用行中第一个按钮的边距。

.button:first-child {
  /* reset margin for the leftmost button */
  margin-left: 0;
}

为了使按钮具有体积感,让我们添加阴影。

诀窍在于按钮元素高于键盘窗格。

.button {
  /* raising the button above the keyboard */
  transform: translate3d(0px, 0px, var(--depth));
}

因此,阴影必须直接放置在窗格上。定义如下:

.button .shadow {
  /* centering the shadow */
  position: absolute;
  left: 50%;
  top: 50%;
  /* applying the transform */
  transform: translate3d(-50%, -50%, calc(var(--depth) * -1));
  background: #00000088;
}

因此,键盘看起来就像一个 3D 物体。

尽管这种方法有缺点——从某些角度看,按钮就像漂浮在键盘上方一样清晰可见。

发光按钮

您可能注意到,我添加了个人发光徽标而不是 Mac/Windows 徽标。

发光效果是通过 text-shadow 规则创建的。看一下具体实现:

/* settings for the special button */
.button.dev {
  /* defining the accent color */
  --accent: #ffffff;
  color: var(--accent);
  /* adjusting letter spacing for the better readability */
  letter-spacing: 0.5px;
  /* adding the glow effect */
  text-shadow:
    0 0 5px var(--accent),
    0 0 10px var(--accent),
    0 0 15px var(--accent),
    0 0 20px var(--color),
    0 0 30px var(--color),
    0 0 40px var(--color),
    0 0 50px var(--color),
    0 0 75px var(--color);
}

颜色在全局变量部分定义(摘录):

:root {
  /* letter color */
  --color: yellow;
}

因此,通过改变全局颜色变量,您可以改变整个键盘的外观!

您可以在开发人员工具中使用颜色。

JS文档

在我熟悉 TypeScript 之前,我大量使用JSDoc符号。

JSDoc 不能代替 TypeScript,因为它仅提示预期数据的类型。

在这个项目中,我使用了 JSDoc 的一些特性:定义了几种类型,设置了函数参数的类型和返回类型。

句法

对语法的介绍很少。

所有 JSDoc 定义都必须用附加星号包裹在标准多行注释中。

/**
 */

JSDoc 通过标签进行操作。有些标签是单个的,而有些标签可以接收参数列表。

我们通过例子来解释一下。@typedef标签定义了一个名为myType的对象类型。

/**
 * @typedef {Object} myType
 */

一些 JSDoc 标签可以被视为另一个标签的一部分。在我们的例子中,名为@property的标签是@typedef标签的一部分

/**
 * @typedef {Object} myType
 * @property {string} value the value
 */

JSDoc 的另一个很酷的功能是,我们可以将字段定义为可选的。这可以通过用方括号包裹名称来实现:

/**
 * @typedef {Object} myType
 * @property {Object} [data] an optional data
 */

通过使用混合类型可以实现更高程度的灵活性。如果参数可以是多种类型,则语法将如下所示:

/**
 * @typedef {Object} myType
 * @property {string | string[]} list the list of items
 */

现在,让我们看一下我使用过的类型。

类型

首先,我们可以定义一个自定义类型来描述按钮的值。它看起来像这样:

/**
 * @typedef {Object} key
 * @property {string} [extra] extra class name
 * @property {string | string[]} value button label(s)
 */

下一个事实是用户定义类型可以用作其他类型定义的一部分。

/**
 * @typedef {Object} section
 * @property {string} [extra] extra class name
 * @property {key[]} keys set of keys in the row
 */

因此,在这个例子中,我们定义了key类型。稍后,我们将key[]类型设置为type section的参数keys

替代文本

从上面的截图中可以看出,keys 参数保留了它的类型以及描述。

返回类型

JSDoc 还可以定义返回值的类型,下面是一个例子:

/**
 * create new div element
 * @returns {HTMLDivElement}
 */
function div() {
  return document.createElement('div');
}

IDE 将根据函数返回的类型来处理变量:

替代文本

类型化参数

此外,JSDoc 允许定义函数参数的类型。

/**
 * parse the array of strings and build a string from the values
 * @param {string[]} values values to be parsed
 * @returns {string}
 */
function toString(values) {
  return values.filter(value => !!value).join(' ');
}

因此我们可以对预期数据有一个提示:

替代文本

尽管代码开发过程变得简单且整体有所改进,但我强烈建议使用 TypeScript 进行严格的静态类型检查。

JavaScript

现在,我们已经创建了布局,为元素定义了 CSS 规则并声明了类型,是时候添加数据和一些功能了。

键盘分为两部分:主键盘和附加键盘。每组数据都带有相应的 JSDoc 标签:

/**
 * the list of buttons of the additional section
 * @type {section[]}
 */
const additionalSection = [
  /* the data goes here */
];

您可以在我创建的要点中查看完整声明

现在,介绍功能。

第一个函数用于过滤字符串数组并通过空格符号连接其余值。

function toString(values) {
  return values.filter(value => !!value).join(' ');
}

下一个函数是一个包装器,用于获取正确的类型:

/**
 * create new div element
 * @returns {HTMLDivElement}
 */
function div() {
  return document.createElement('div');
}

最后一个函数解析初始数据,构建所有元素,并应用 CSS 样式。

首先,让我们找到容器。

const container = document.getElementById('container');

如果容器存在,我们就开始构建元素。

这个过程很简单——构建元素,将其附加到父元素。

代码将如下所示(摘录):

/**
 * draw a section
 * @param {section[][]} sections list of sections to be drawn
 */
function draw(sections) {
  // obtaining the container
  const container = document.getElementById('container');

  if (container) {
    // creating keyboard
    const keyboard = div();
    keyboard.className = 'keyboard';

    /* the rest of the logic */

    // appending the keyboard to the container
    container.appendChild(keyboard);
  }
}

上述例程与元素的每一级创建都是嵌套的。

示意图如下:

创建键盘
  创建部分
    创建行
      创建按钮
      向行添加一个按钮
    向部分添加一行
  将一个部分添加到键盘
将键盘附加到容器

还有一件事需要彻底解释,那就是按钮标签的渲染。

你还记得类型的参数有混合类型吗?

/**
 * @property {string | string[]} value button label(s)
 */

为了跳过渲染期间的类型检查,我们将任何值转换为数组:

// turn any value into an array
const value = key.value instanceof Array ? key.value : [key.value];
// rendering labels
value.forEach(item => {
  const label = div();
  label.innerText = item || '';
  button.appendChild(label);
});

结论

所提出的解决方案轻量、简单且灵活。

尽管这个例子的设计有一些缺点:

  • 按钮的体积视图是模拟的,因此,从某些角度看,按钮就像漂浮在键盘上方一样清晰可见。

  • 各部分的宽度由其内容定义。因此,部分可能会溢出键盘容器。因此,您需要在每个实现中调整每行按钮的大小和数量。

  • 无法定义垂直按钮。是的,唯一可用的选项是设置不同的宽度。

请不要因此而对我太苛刻——我在大约 20 分钟内创建了这个示例,因为我需要为这篇文章制作一张封面图片。

更新

这条评论之后

我加了一支笔!

我希望这支笔能帮助你了解事物的运作方式。

此外,修改参数也会更容易。

祝您黑客愉快!

鏂囩珷鏉ユ簮锛�https://dev.to/peacefullatom/the-3d-keyboard-made-with-css-and-javascript-280
PREV
Desafio:Criar um App em React Native Usando Apenas ChatGPT
NEXT
CSS 3D 布局代码详细信息结论 AWS 安全 LIVE!