写点什么

web 前端培训如何使用 CSS 连接数据库

作者:@零度
  • 2022 年 4 月 15 日
  • 本文字数:3681 字

    阅读完需:约 12 分钟

 以下文章来源于前端开发

它是如何工作的?

首先我们需要用到一组被亲切地称为 Houdini 的 api,它让你的浏览器能够通过 Javascript 对象模型来控制 CSS。换言之,这意味着您可以定制 CSS 样式、添加定制属性,等等。

可能这个作品最大的特性是 CSS Paint Worklet,它允许你在一个元素上“绘制”,就像你知道和喜欢的画布一样,并让浏览器把它当作 CSS 中的图像。这里有一些例子可以用来演示 Houdini

然而,这个工作集只提供了 Worker API 的一个子集,而且画布上下文本身也被大量剥离。这样做的实际结果是,您的自定义 CSS 绘制代码提供了一个比您预期的更小的沙盒。

这意味着什么? 没有网络访问权限,因此可以和 fetch 和 XmlHttpRequest 说再见了。在绘制上下文上没有 drawText 功能。其他各种 JS api 也消失了,以防你希望解决这些问题。

不过,不用担心。并非一切都完了。让我们把它分解成几个步骤。


1. 设置数据库

这必须是第一步,以便理解概念证明是否可行。

首先我们会借助于 sql.js。它实际上是一个通过 emscripten 编译成 WebAssembly 和老式 ASM.js 的 SQLite 版本。不幸的是,我们不能使用 WASM 版本,因为它必须通过网络获取二进制文件。ASM 版本没有这个限制,因为所有的代码都可以在一个模块中使用_前端培训

虽然 PaintWorklet 限制了 worker 内部的网络访问,但你仍然可以导入代码,只要它是一个 ES6 模块。这意味着文件中必须有一个导出语句。不幸的是,sql.js 没有 ES6 的版本,所以我自己修改了 sql.js,使其能够顺利的被 import 进入项目。

现在到了关键时刻:我可以在我的工作包中建立一个数据库吗?

const SQL = await initSqlJs({

locateFile: file => `./${file}`,

});

const DB = new SQL.Database();

**成功了!**但没有任何数据,所以我们来解决这个问题。

2. 查询数据库

一开始最简单的方法就是设置一些假数据,sql.js 有两个函数可以做到这一点。

DB.run('CREATE TABLE test (name TEXT NOT NULL)')

DB.run(

'INSERT INTO test VALUES (?), (?), (?), (?)',

['A', 'B', 'C', 'D']

)

我有了测试表,里面有一些值。我应该能够查询这个并获得这些值,尽管我不确定得到什么样的结构化查询结果。

const result = DB.exec('SELECT * FROM test')

console.log(result)

正如预期的那样,结果已经出来了。不过,渲染展示通过 CSS 查询数据库的结果会更好。

3. 渲染结果,最简单的方法

我认为这就像在画布上写文本一样。这有多难,对吧?

class SqlDB {

async paint(ctx, geom, properties) {

const result = DB.exec('SELECT * FROM test');

ctx.font = '32px monospace';

ctx.drawText(JSON.stringify(result), 0, 0, geom.width);

}

}

不,那样就太简单了。这里的上下文与画布元素的上下文不同,它只提供了功能的一个子集。

当然,它仍然可以绘制路径和曲线,所以缺乏方便的 API 是一个障碍,但这一切都不是问题。

4. 不使用文本 API 创建文本

幸运的是,我们可以借助于 opentype.js[6]所提供的解决方案。它可以解析一个字体文件,然后,给定一个文本字符串,生成每个字符的字母形式。这个操作的实际结果是一个表示字符串的路径对象,然后可以将其呈现到上下文中。

这次我不必修改 opentype 库来导入它,因为它已经可以从 JSPM[7]中获得。所以,如果你给 JSPM 一个 npm 包,它会自动生成一个 ES6 模块,你可以直接导入到你的浏览器中。这是非常棒的,因为我真的不想为了一个有趣的项目而使用打包工具。

import opentype from 'https://ga.jspm.io/npm:opentype.js@1.3.4/dist/opentype.module.js'

opentype.load('fonts/firasans.otf')

但这里有一个问题——它想通过网络加载字体,而我不能这样做!嗨,挫败了!

……而且?它还有一个接受数组缓冲区的解析方法。我将用 base64 编码字体,然后在我的模块中解码它。

import opentype from 'https://ga.jspm.io/npm:opentype.js@1.3.4/dist/opentype.module.js'

import base64 from 'https://ga.jspm.io/npm:base64-js@1.5.1/index.js'

const font = 'T1RUTwAKAIAAAwA ... 3 days later ... wAYABkAGgAbABwAIAKM'

export default opentype.parse(base64.toByteArray(font).buffer)

我告诉过你 worklet 也没有处理 base64 字符串的 api 吗?atob 和 btoa 都没有!我也不得不为此找到一个普通的 JS 实现。

我把这段代码放在它自己的文件中,因为它不太符合人体工程学……必须在剩下的代码旁边使用大约 200kb 的编码字体字符串。

这就是我为何要滥用 ES 模块来加载我的字体的原因。

5. 渲染结果,另一种简单的方式

从现在起,所有繁重的工作都由 opentype 库来完成,所以我所需要做的就是用一点数学知识来对齐。

import font from './font.js'

const SQL = await initSqlJs({

locateFile: file => `./${file}`,

});

const DB = new SQL.Database();

DB.run('CREATE TABLE test (name TEXT NOT NULL)')

DB.run(

'INSERT INTO test VALUES (?), (?), (?), (?)',

['A', 'B', 'C', 'D']

)

class SqlDB {

async paint(ctx, geom, properties) {

const query = DB.exec('SELECT * FROM test')

const result = query[0].values.join(', ')

const size = 48

const width = font.getAdvanceWidth(queryResults, size)

const point = {

x: (geom.width / 2) - (width / 2),

y: geom.height / 2

}

const path = font.getPath(result, point.x, point.y, size)

path.draw(ctx)

}

}

registerPaint('sql-db', SqlDb)

最好再来一些 HTML 和 CSS 看看发生了什么。

<html>

<head>

<script>

CSS.paintWorklet.addModule('./cssdb.js')

</script>

<style>

main {

width: 100vw;

height: 100vh;

background: paint(sql-db);

}

</style>

</head>

<body>

<main></main>

</body>

</html>

成功了!但这里没有足够的 CSS,而且查询是硬编码的。

6. 通过 CSS 查询

如果必须使用 CSS 来查询数据库,那就更好了。事实上,这是我们可以在 Paint Worker 的上下文之外与其通信的唯一方式,因为没有与 Web worker 一样的消息传递 API。

为此,需要一个定制的 CSS 属性。定义 inputProperties 的好处是可以订阅该属性的更改,因此如果该属性的值发生更改,它将重新呈现。不需要设置任何订阅者自己。

class SqlDb {

static get inputProperties() {

return [

'--sql-query',

]

}

async paint(ctx, geom, properties) {

// ...

const query = DB.exec(String(properties.get('--sql-query')))

}

}

这些 CSS 属性被称为类型化属性,但它们本质上被封装在一个特殊的 CSSProperty 类中,而这个类本身并不是很有用。因此,你必须手动将其转换为字符串或数字或其他类似的使用它,如上所述。

现在对 CSS 做一个快速调整。

main {

// ...

--sql-query: SELECT name FROM test;

}

引号在这里被故意省略了,因为否则在将字符串传递给数据库之前,我必须将它们从字符串中删除。也就是说,这很有效!

任务完成!

如果你玩过 sqlcss。你会注意到我并没有满足于此。在进行了一些重构之后,又进行了一些更改。

7. 导入数据库文件

硬编码数据库模式和实际数据,有点糟糕。它证明了这个概念,但我们肯定可以做得更好。

如果您可以查询任何您喜欢的数据库,只要您手边有数据库文件,那就太棒了。我只需要读取这个文件并对其进行 base64 编码,就像我对字体文件所做的那样。

const fileInput = document.getElementById('db-file')

fileInput.onchange = () => {

const reader = new FileReader()

reader.readAsDataURL(fileInput.files[0])

reader.onload = () => {

document.documentElement.style.setProperty(

'--sql-database',

`url("${reader.result}")`

)

}

}

我为此做了一个额外的 CSS 属性,在这个属性中,您可以将 SQLite 数据库作为 base64 编码的数据 URI 提供。data URI 只是为了显示并确保它对 DOM 是有效的,我将在 Worker 层面解析这些东西。

最后一步是使其更易于查询,因为否则您必须进入调试器来操作元素的 CSS。

8. 编写查询语句

这可能是项目中最简单的部分。自定义属性对于分号有一点问题,而 SQLite 并不关心末尾的分号是否被省略,所以最简单的做法是,如果在输入中找到它,就删除它。

const queryInput = document.getElementById('db-query')

queryInput.onchange = () => {

let query = queryInput.value;

if (query.endsWith(';')) {

query = query.slice(0, -1)

}

document.documentElement.style.setProperty(

'--sql-query',

queryInput.value

)

}

从现在开始,您可以使用 CSS 导入和浏览您自己的数据库了!


我遗漏了一件事,就是所有这些查询结果特别多的时候,如何更好的渲染展示的问题。如果查询结果有很多,他们需要分开到单独的行。这与本文的主题--使用 CSS 连接到数据库并没有太大关系,所以我认为在这里谈论这个问题并不合适,但如果你想进一步了解这个"荒谬"的概念,git 上的代码都是可用的[8]

译者补充

  1. SQLite 是由 C 语言编写的嵌入式轻量级数据库,广泛用于 IOS 和 Android 的 App 中。若有读者想要在浏览器端应用 SQLite,推荐使用 SQLite 的 WebAssembly 版--sql.js,基本与 SQLite 的使用没有太多区别。在实际项目使用中,还需特别注意 SQLite 的安全性问题。

  2. 为方便展示,特此截取本文作者所实现的 CSS 连接 SQLite 的部分 demo 如下:


3.关于文中所提到的 jspm,这里引用其官网的介绍如下:jspm 为导入映射提供了模块 CDN 和包管理,允许任何来自 NPM 的包都可以直接在浏览器中加载,无需进一步的处理。


用户头像

@零度

关注

关注尚硅谷,轻松学IT 2021.11.23 加入

IT培训 www.atguigu.com

评论

发布
暂无评论
web前端培训如何使用CSS连接数据库_CSS_@零度_InfoQ写作平台