Skip to content

WebAssembly 入门

【WASM 入门与实战】使用 Rust 封装一个 WASM NPM 包 - 掘金 (juejin.cn)

浏览器第四种语言-WebAssembly - 掘金 (juejin.cn)

WebAssembly:开启新时代的跨平台 - 掘金 (juejin.cn)

走进 WebAssembly 的世界 - 字节前端的专栏 - 掘金 (juejin.cn)

WebAssembly:未来前端开发的必备技能 - 掘金 (juejin.cn)

使用 WebAssembly 来增强你的前端应用 - 掘金 (juejin.cn)

前端视频帧提取 ffmpeg + Webassembly - 掘金 (juejin.cn)

如何在应用里使用 WebAssembly?

WebAssembly 给 Web 平台添加了两块内容:一种二进制格式代码,以及一系列可用于加载和执行二进制代码的 API。

WebAssembly 目前处于一个萌芽的节点,之后肯定会涌现出很多工具,而目前有四个主要的入口:

  • 使用 EMScripten 来移植 C/C++ 应用
  • 在汇编层面直接编写和生成 WebAssembly 代码
  • 编写 Rust 应用,然后将 WebAssembly 作为它的输出
  • 使用 AssemblyScript,它是一门类似 TypeScript 的语言,能够编译成 WebAssembly 二进制

WebAssembly,移植 C/C++ 应用(后面的教程都可以)

Rust 和 WebAssembly 书籍

介绍 - Rust 和 WebAssembly (llever.com)

Rust WebAssembly 实现视频实时滤镜效果 - 掘金 (juejin.cn)

解锁前端新潜能:如何使用 Rust 锈化前端工具链 - 掘金 (juejin.cn)

WASM-BINDGEN 搭起 Rust 与 JavaScript 的桥梁 (qq.com)

前端Rust生态的背后,napi-rs如何让Rust与JavaScript可以相互调用 - 掘金 (juejin.cn)

MDN 教程文档(包括 C/C++和 Rust 语言编写 WebAssembly 的教程)WebAssembly | MDN (mozilla.org)

i5ting/learn-rust-for-fe: Rust 是未来前端基础设施 (github.com)

揭秘 Rust Sycamore 如何革新前端开发,打造极速响应式 SPA【耗血整理】 (qq.com)

使用Rust和WebAssembly整花活儿(一)——快速开始 - 掘金 (juejin.cn)

使用Rust和WebAssembly整花活儿(二)——DOM和类型转换 - 掘金 (juejin.cn)

使用Rust和WebAssembly整花活儿(三)——Rust与JS交互 - 掘金 (juejin.cn)

使用Rust和WebAssembly整花活儿(四)——更小更小的wasm文件体积 - 掘金 (juejin.cn)

WebAssembly 官网

网址:WebAssembly

资源社区:资源 - WebAssembly

中文网:WebAssembly中文网 | WebAssembly中文网 (wasmdev.cn)

视频学习

学习 WebAssembly:实用指南 |从零到精通 (zerotomastery.io)

探索进阶主题

当掌握了 WebAssembly 的基础知识后,可以进一步探索一些进阶主题。例如,如何在 JavaScript 中使用 WebAssembly、如何优化 WebAssembly 代码的性能等。

支持WebAssembly的语言

Webassembly支持哪些语言? | WebAssembly中文网 (wasmdev.cn)

WebAssembly作为一项新兴的技术,已经发展至2.0版本。它具有跨平台、高效、安全的优点,可以在各种计算机架构上运行,并且具有接近原生代码的性能。目前,许多语言已经支持了WebAssembly,让开发者能够更方便地将不同语言编写的代码部署到Web端。以下是一些常用的WebAssembly开发语言:

  1. C/C++: C/C++是WebAssembly的先行者,拥有庞大的用户群体和完备的工具链。LLVM已经将WebAssembly添加为受支持的后端,同时WebAssembly System Interface标准(WASI)使得WebAssembly的能力从Web端拓展到了非Web端,服务端成为了WebAssembly下一个热门的发展方向。
  2. Rust: Rust是WebAssembly的目标语言之一,通过LLVM生成WebAssembly产物。Rust兼容C/C++的内存模型,无GC,因此生成的WebAssembly产物体积小巧性能良好。Rust同样支持WASI标准,非Web端应用也成了Rust WebAssembly生态发展的热门方向。
  3. Go: Go从1.11版本开始正式支持WebAssembly,但存在一些局限性,如不支持WASI和体积膨胀。尽管如此,Go作为服务端领域的主流语言,仍在WebAssembly社区中有一定影响力。
  4. AssemblyScript: AssemblyScript是专为WebAssembly服务的语言,使用类似TypeScript的语法。它方便前端开发者编写WebAssembly项目,但生成的WebAssembly产物相对臃肿。
  5. JavaScript: JavaScript作为前端领域必不可少的语言,与WebAssembly可以无缝交互。通过WebAssembly的JavaScript API,你可以在同一个应用中利用WebAssembly的性能和能力以及JavaScript的表达力和灵活性。

除了上述语言,还有其他一些主流语言以各种方式支持了WebAssembly,例如Lua、C#、Kotlin等。WebAssembly的发展潜力巨大,未来还有许多值得期待的方向,如多线程支持、AI模型的推理和训练,以及在区块链领域的智能合约编写和执行

运行时执行

就像编译 wasm 的方式有很多种一样,执行它的方法也有很多种。

在浏览器中使用WebAssembly

要在浏览器中使用WebAssembly API,首先加载WebAssembly模块:

javascript
// fetch .wasm file
const response = fetch('/path/to/some.wasm')

// instantiate module with streaming
const module = WebAssembly.instantiateStreaming(response)

然后,调用导出的add的函数:

java
const result = module.instance.exports.add(1, 2)

还可以向模块传递其他方法,例如可以共享 console.log 方法:

javascript
// fetch .wasm file
const response = fetch('/path/to/some.wasm')

// instantiate module and pass an api
const module = WebAssembly.instantiateStreaming(response, {
  imports: {
    // share console.log
    log: console.log
  }
  
})

在服务器上使用 WebAssembly

WebAssembly也可以在服务器上执行,使用的方法与在浏览器中的几乎相同。

唯一不同的是,不使用URL从服务器获取.wasm文件,而是使用 fs.readFile() 从磁盘上读取它:

javascript
import fs from 'fs'

// read .wasm file
const bytes = await fs.promises.readFile('/path/to/some.wasm')

// instantiate the module
const module = WebAssembly.instantiate(bytes)

然后,调用 add 函数:

java
const result = module.instance.exports.add(1, 2)

还可以从许多其他编程语言中执行此操作,例如 rust[5] *、* ruby[6]python[7] 或来自 CLI[8]

在云端使用 WebAssembly

WebAssembly在云端也有一些重要的应用场景。

与JavaScript云函数相比,它具备一些明显的优势:

  1. 无冷启动:主机只需加载一个.wasm文件,而不是整个应用程序;典型的JavaScript应用包含许多需要加载的文件,这会耗费较长的时间。
  2. 更快的部署:需要上传的仅仅是一个简单的二进制文件。
  3. 多语言托管:所有可以编译为WebAssembly的编程语言都可以在云中部署,无需特殊的运行时环境。
  4. 快照:执行状态可以进行快照;例如,一个在初始化期间执行大量计算的应用程序可以创建快照。随后的请求可以从快照状态开始,从而避免浪费大量的启动时间。

120 行代码实现纯 Web 剪辑视频

什么是 webasembly?

WebAssembly(wasm)就是一个可移植、体积小、加载快并且兼容 Web 的全新格式。可以将 C,C++等语言编写的模块通过编译器来创建 wasm 格式的文件,此模块通过二进制的方式发给浏览器,然后 js 可以通过 wasm 调用其中的方法功能。

WebAssembly 的优势

网上对于这个相关的介绍应该有很多了,WebAssembly 优势性能好,运行速度远高于 Js,对于需要高计算量、对性能要求高的应用场景如图像/视频解码、图像处理、3D/WebVR/AR 等,优势非常明显,们可以将现有的用 C、C++等语言编写的库直接编译成 WebAssembly 运行到浏览器上,并且可以作为库被 JavaScript 引用。那就意味着我们可以将很多后端的工作转移到前端,减轻服务器的压力。.........

WebAssembly 最简单的实践调用

我们编写一个最简单的 c 文件

c
int add(int a,int b) {
  return a + b;
}

然后安装对于的 Emscripten 编译器 Emscripten 安装指南

bash
emcc test.c -Os -s WASM=1 -s SIDE_MODULE=1 -o test.wasm

然后我们在 html 中引入使用即可

js
fetch("./test.wasm")
	.then((response) => response.arrayBuffer())
	.then((bytes) => WebAssembly.instantiate(bytes))
	.then((results) => {
		const add = results.instance.exports.add;
		console.log(add(11, 33));
	});

这时我们即可在控制台看到对应的打印日志,成功调用我们编译的代码啦

正式开动

既然我们已经知道如何能快速的调用到一些已经成熟的 C,C++的类库,那我们离在线剪辑视频预期目标更进一步了。

最终 demo 演示

由于录制操作的电脑 cpu 不太行,所以可能耗时比较久,但整体的效果还是能看的到滴

demo 仓库地址:https://github.com/Dseekers/clip-video-by-webassembly

图片

FFmpeg

在这个之前你得稍微的了解下啥是 FFmpeg? 以下根据维基百科的目录解释

FFmpeg 是一个开放源代码的自由软件,可以运行音频和视频多种格式的录影、转换、流功能[1],包含了 libavcodec——这是一个用于多个项目中音频和视频的解码器库,以及 libavformat——一个音频与视频格式转换库。

简单的说这个就是由 C 语言编写的视频处理软件,它的用法也是相当滴简单

我主要将这次需要用到的命令给调了出来,如果你还可能用到别的命令,可以根据他的官方文档查看 ,还可以了解下阮一峰大佬的文章 (https://www.ruanyifeng.com/blog/2020/01/ffmpeg.html)

bash
ffmpeg -ss [start] -i [input] -to [end] -c copy [output]

start 为开始时间 end 为结束时间 input 为需要操作的视频源文件 output 为输出文件的位置名称

这一行代码就是我们需要用到的剪辑视频的命令了

获取相关的 FFmpeg 的 wasm

由于通过 Emscripten 编译 ffmpeg 成 wasm 存在较多的环境问题,所以我们这次直接使用在线已经编译好的 CDN 资源

这边就直接使用了这个比较成熟的库 https://github.com/ffmpegwasm/ffmpeg.wasm

为了本地调试方便,我把其相关的资源都下了下来 一共 4 个资源文件

js
ffmpeg.min.js;
ffmpeg - core.js;
ffmpeg - core.wasm;
ffmpeg - core.worker.js;

我们使用的时候只需引入第一个文件即可,其它文件会在调用时通过 fetch 方式去拉取资源

最小的功能实现

前置功能实现: 在我们本地需要实现一个 node 服务,因为使用 ffmpeg 这个模块会出现如果没在服务器端设置响应头, 会报错 SharedArrayBuffer is not defined,这个是因为系统的安全漏洞,浏览器默认禁用了该 api,若要启用则需要在 header 头上设置

bash
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

我们启动一个简易的 node 服务

js
const Koa = require("koa");
const path = require("path");
const fs = require("fs");
const router = require("koa-router")();
const static = require("koa-static");
const staticPath = "./static";
const app = new Koa();
app.use(static(path.join(__dirname, staticPath)));
// log request URL:
app.use(async (ctx, next) => {
	console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
	ctx.set("Cross-Origin-Opener-Policy", "same-origin");
	ctx.set("Cross-Origin-Embedder-Policy", "require-corp");
	await next();
});

router.get("/", async (ctx, next) => {
	ctx.response.body = "<h1>Index</h1>";
});
router.get("/:filename", async (ctx, next) => {
	console.log(ctx.request.url);
	const filePath = path.join(__dirname, ctx.request.url);
	console.log(filePath);
	const htmlContent = fs.readFileSync(filePath);
	ctx.type = "html";
	ctx.body = htmlContent;
});
app.use(router.routes());
app.listen(3000);
console.log("app started at port 3000...");

我们做一个最小化的 demo 来实现下这个剪辑功能,剪辑视频的前一秒钟 新建一个 demo.html 文件,引入相关资源

html
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script src="./assets/ffmpeg.min.js"></script>

<div class="container">
	<div class="operate">
		选择原始视频文件:
		<input type="file" id="select_origin_file" />
		<button id="start_clip">开始剪辑视频</button>
	</div>
	<div class="video-container">
		<div class="label">原视频</div>
		<video class="my-video" id="origin-video" controls></video>
	</div>
	<div class="video-container">
		<div class="label">处理后的视频</div>
		<video class="my-video" id="handle-video" controls></video>
	</div>
</div>
let originFile $(document).ready(function () {
$('#select_origin_file').on('change', (e) => { const file = e.target.files[0]
originFile = file const url = window.webkitURL.createObjectURL(file)
$('#origin-video').attr('src', url) }) $('#start_clip').on('click', async
function () { const { fetchFile, createFFmpeg } = FFmpeg; ffmpeg =
createFFmpeg({ log: true, corePath: './assets/ffmpeg-core.js', }); const file =
originFile const { name } = file; if (!ffmpeg.isLoaded()) { await ffmpeg.load();
} ffmpeg.FS('writeFile', name, await fetchFile(file)); await ffmpeg.run('-i',
name, '-ss', '00:00:00', '-to', '00:00:01', 'output.mp4'); const data =
ffmpeg.FS('readFile', 'output.mp4'); const tempURL = URL.createObjectURL(new
Blob([data.buffer], { type: 'video/mp4' })); $('#handle-video').attr('src',
tempURL) }) });

其代码的含义也是相当简单,通过引入的 FFmpeg 去创建一个实例,然后通过 ffmpeg.load()方法去加载对应的 wasm 和 worker 资源 没有进行优化的 wasm 的资源是相当滴大,本地文件竟有 23MB,这个若是需要投入生产的可是必须通过 emcc 调节打包参数的方式去掉无用模块。然后通 fetchFile 方法将选中的 input file 加载到内存中去,接下来就可以通过 ffmpeg.run 运行和 本地命令行一样的 ffmpeg 命令行参数了参数基本一致

这时我们的核心功能已经实现完毕了。

做一点小小的优化

剪辑的话最好是可以选择时间段,我这为了方便直接把 element 的以 cdn 方式引入使用 通过 slider 来截取视频区间,我这边就只贴 js 相关的代码了,具体代码可以去 github 仓库里面仔细看下

js
class ClipVideo {
	constructor() {
		this.ffmpeg = null;
		this.originFile = null;
		this.handleFile = null;
		this.vueInstance = null;
		this.currentSliderValue = [0, 0];
		this.init();
	}
	init() {
		console.log("init");
		this.initFfmpeg();
		this.bindSelectOriginFile();
		this.bindOriginVideoLoad();
		this.bindClipBtn();
		this.initVueSlider();
	}
	initVueSlider(maxSliderValue = 100) {
		console.log(`maxSliderValue ${maxSliderValue}`);
		if (!this.vueInstance) {
			const _this = this;
			const Main = {
				data() {
					return {
						value: [0, 0],
						maxSliderValue: maxSliderValue,
					};
				},
				watch: {
					value() {
						_this.currentSliderValue = this.value;
					},
				},
				methods: {
					formatTooltip(val) {
						return _this.transformSecondToVideoFormat(val);
					},
				},
			};
			const Ctor = Vue.extend(Main);
			this.vueInstance = new Ctor().$mount("#app");
		} else {
			this.vueInstance.maxSliderValue = maxSliderValue;
			this.vueInstance.value = [0, 0];
		}
	}
	transformSecondToVideoFormat(value = 0) {
		const totalSecond = Number(value);
		let hours = Math.floor(totalSecond / (60 * 60));
		let minutes = Math.floor(totalSecond / 60) % 60;
		let second = totalSecond % 60;
		let hoursText = "";
		let minutesText = "";
		let secondText = "";
		if (hours < 10) {
			hoursText = `0${hours}`;
		} else {
			hoursText = `${hours}`;
		}
		if (minutes < 10) {
			minutesText = `0${minutes}`;
		} else {
			minutesText = `${minutes}`;
		}
		if (second < 10) {
			secondText = `0${second}`;
		} else {
			secondText = `${second}`;
		}
		return `${hoursText}:${minutesText}:${secondText}`;
	}
	initFfmpeg() {
		const { createFFmpeg } = FFmpeg;
		this.ffmpeg = createFFmpeg({
			log: true,
			corePath: "./assets/ffmpeg-core.js",
		});
	}
	bindSelectOriginFile() {
		$("#select_origin_file").on("change", (e) => {
			const file = e.target.files[0];
			this.originFile = file;
			const url = window.webkitURL.createObjectURL(file);
			$("#origin-video").attr("src", url);
		});
	}
	bindOriginVideoLoad() {
		$("#origin-video").on("loadedmetadata", (e) => {
			const duration = Math.floor(e.target.duration);
			this.initVueSlider(duration);
		});
	}
	bindClipBtn() {
		$("#start_clip").on("click", () => {
			console.log("start clip");
			this.clipFile(this.originFile);
		});
	}
	async clipFile(file) {
		const { ffmpeg, currentSliderValue } = this;
		const { fetchFile } = FFmpeg;
		const { name } = file;
		const startTime = this.transformSecondToVideoFormat(currentSliderValue[0]);
		const endTime = this.transformSecondToVideoFormat(currentSliderValue[1]);
		console.log("clipRange", startTime, endTime);
		if (!ffmpeg.isLoaded()) {
			await ffmpeg.load();
		}
		ffmpeg.FS("writeFile", name, await fetchFile(file));
		await ffmpeg.run(
			"-i",
			name,
			"-ss",
			startTime,
			"-to",
			endTime,
			"output.mp4"
		);
		const data = ffmpeg.FS("readFile", "output.mp4");
		const tempURL = URL.createObjectURL(
			new Blob([data.buffer], { type: "video/mp4" })
		);
		$("#handle-video").attr("src", tempURL);
	}
}
$(document).ready(function () {
	const instance = new ClipVideo();
});

这样文章开头的效果就这样实现啦

Rust 利用 wasm-bindgen 做一个简化 web 计算器

入门

wasm-bindgen是一个 Rust 库和工具,旨在促进 WebAssembly 模块与 JavaScript 之间的高级交互。它允许开发者在 Rust 中定义 JavaScript 类,或者从 JavaScript 获取和返回复杂的类型,如字符串、对象等。本教程将指导你如何使用wasm-bindgen来创建和部署一个 Rust 编写的 WebAssembly 应用。

步骤 1: 设置 Rust 环境

确保你已经安装了 Rust 和 Cargo。如果没有,请访问 Rust 官网 进行安装。

步骤 2: 创建新的 Rust 项目

在命令行中创建一个新的 Rust 库项目,并将其指定为 WebAssembly 目标:

bash
cargo new my_project --lib

my_project替换为你的项目名称。

步骤 3: 编辑 Cargo.toml

在你的Cargo.toml文件中,添加wasm-bindgen依赖项:

toml
[dependencies]
wasm-bindgen = "0.2"

步骤 4: 编写 Rust 代码

在你的src/lib.rs文件中,使用wasm-bindgen宏定义 Rust 函数和 JavaScript 交互:

rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn say_hello(name: &str) {
	web_sys::console::log_1(&wasm_bindgen::JsValue::from_str(&format!("Hello, {}!", name)));
}

这里我们定义了一个say_hello函数,它接受一个字符串参数name,并使用web_sysconsole::log函数在浏览器控制台输出一条问候信息。

步骤 5: 编译 Rust 代码为 WebAssembly

使用以下命令编译你的 Rust 项目为 WebAssembly 模块:

bash
cargo build --target wasm32-unknown-unknown

编译完成后,你会在target/wasm32-unknown-unknown/debug/my_project.wasm找到编译后的文件。

步骤 6: 使用 wasm-bindgen 生成 JavaScript 绑定

安装wasm-bindgen-cli工具,并使用它来生成 JavaScript 绑定:

bash
cargo install wasm-bindgen-cli

wasm-bindgen target/wasm32-unknown-unknown/debug/my_project.wasm --out-dir ./pkg --target web

这将生成一个pkg目录,其中包含与你的 WebAssembly 模块交互所需的 JavaScript 文件。

步骤 7: 在 Web 应用中使用 WebAssembly 模块

在你的 HTML 文件中,添加一个脚本标签来加载编译后的 WebAssembly 模块:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>My Rust Wasm App</title>
		<script src="pkg/my_project.js"></script>
	</head>
	<body>
		<script>
			my_project.say_hello("World"); // 调用 Rust 函数
		</script>
	</body>
</html>

现在,当你在浏览器中打开这个 HTML 文件时,你应该会在控制台看到输出的问候信息。

额外资源

通过以上步骤,你已经成功地创建并运行了一个使用wasm-bindgen的 Rust WebAssembly 应用。你可以继续探索wasm-bindgen的更多功能,如使用JsCast进行类型转换、wasm-bindgen-futures处理异步操作等,以增强你的应用功能。

简单版计算器

我们可以设计并实现一个复杂的 Web 计算器项目,它不仅支持基本的加减乘除运算,还包括更高级的数学功能,如指数、对数、三角函数等。这个项目将使用 WebAssembly (Wasm) 和 JavaScript 结合来实现。

项目概述

计算器项目将包含以下特性:

  • 基本运算:加法、减法、乘法、除法。

  • 高级运算:指数、对数、平方根、三角函数等。

  • 用户界面:清晰的输入输出显示,操作按钮。

  • 错误处理:处理除以零等错误情况。

  • 历史记录:保存用户的计算历史。

技术栈

  • Rust:作为 WebAssembly 的编写语言,因为它提供了性能和安全性。

  • wasm-bindgen:将 Rust 代码与 JavaScript 互操作。

  • JavaScript:用于创建用户界面和处理用户交互。

  • HTML/CSS/Vue:构建前端页面和样式。

实现步骤

1. Rust 计算器库

创建一个 Rust 库,实现计算器的核心功能。使用wasm-bindgen来暴露 Rust 函数给 JavaScript。

rust
// calculator.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Calculator;

#[wasm_bindgen]
impl Calculator {
pub fn new() -> Calculator {
Calculator
}

// 基本运算

pub fn add(&self, a: f64, b: f64) -> f64 {
 a + b
}

pub fn subtract(&self, a: f64, b: f64) -> f64 {
 a - b
}

pub fn multiply(&self, a: f64, b: f64) -> f64 {
 a * b
}

pub fn divide(&self, a: f64, b: f64) -> Result<f64, &'static str> {
 if b == 0.0 {
 Err("Cannot divide by zero")
 } else {
 Ok(a / b)
 }
}

// 高级运算
pub fn sqrt(&self, a: f64) -> f64 {
 (a).sqrt()
}

pub fn pow(&self, base: f64, exp: f64) -> f64 {
 base.powf(exp)
}
// 更多高级运算可以按需添加...
}

2. 编译为 WebAssembly

使用 Cargo 和 wasm-pack 将 Rust 库编译为 WebAssembly 模块。

bash
wasm-pack build --target web

3. JavaScript 接口

创建 JavaScript 文件来加载和使用 Wasm 模块,并提供与 HTML 界面交互的函数。

javascript
// calculator.js
import * as wasm from "path/to/calculator.js";
async function loadCalculator() {
	return wasmCalculator();
}

function performOperation(calculator, operation, a, b) {
    // 根据操作调用相应的计算器函数
    // 例如:calculator.add(a, b)
}
// 更多 JavaScript 函数来处理用户界面交互...

4. HTML 和 CSS 最简单化地版本

创建 HTML 页面和 CSS 样式,构建用户界面。

html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<title>Complex Calculator</title>
		<link rel="stylesheet" href="styles.css" />
	</head>
	<body>
		<div id="calculator">
			<!-- 输入显示和按钮 -->
		</div>
		<script src="calculator.js"></script>
		<script>
			// JavaScript 逻辑
		</script>
	</body>
</html>

5. 用户界面实现

在 HTML 中添加输入框、显示结果的元素和操作按钮。使用 JavaScript 来处理用户的点击事件,调用相应的计算函数,并显示结果。

vue
<template>
	<div class="calculator">
		<input class="input-number" type="number" v-model="inputValue" />
		<div class="display">{{ result }}</div>
		<button @click="performOperation('add')">+</button>
		<button @click="performOperation('subtract')">-</button>
		<button @click="performOperation('multiply')">*</button>
		<button @click="performOperation('divide')">/</button>
		<button @click="performOperation('sqrt')">√</button>
		<button @click="performOperation('pow', 2)">x²</button>
		<button @click="performOperation('pow', 3)">x³</button>
		<!-- 更多操作按钮 -->
	</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const inputValue = ref("");
const result = ref("");
const performOperation = (operation: string, exponent?: number) => {
	try {
		const value = parseFloat(inputValue.value);
		if (isNaN(value)) {
			throw new Error("Invalid input");
		}
		let newValue: number;
		switch (operation) {
			case "add":
				newValue = value + 1;
				break;
			case "subtract":
				newValue = value - 1;
				break;
			case "multiply":
				newValue = value * 2;
				break;
			case "divide":
				if (value === 0) throw new Error("Cannot divide by zero");
				newValue = value / 2;
				break;
			case "sqrt":
				newValue = Math.sqrt(value);
				break;
			case "pow":
				newValue = Math.pow(value, exponent!);
				break;
			default:
				newValue = value;
		}

		result.value = newValue.toString();
	} catch (error) {
		result.value = "Error";
	}
};
</script>

<style scoped>
.calculator {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 10px;
}
.input-number {
	margin-bottom: 10px;
	padding: 5px;
	font-size: 1rem;
}
.display {
	font-size: 2rem;
	font-weight: bold;
}
button {
	padding: 5px 10px;
	font-size: 1rem;
	margin: 5px;
}
</style>

我们使用v-model指令将inputValue与用户输入绑定,这样我们就可以在performOperation函数中使用这个值来进行计算。

请注意,我们还在performOperation函数中添加了错误处理逻辑,以确保用户输入的是有效的数字,并且在执行除法操作时避免除以零的错误。如果发生错误,结果将显示为"Error"。

部署

将项目文件部署到 Web 服务器或使用静态页面托管服务,如 GitHub Pages。

总结

通过以上步骤,你可以创建一个功能简洁的 Web 计算器应用,它利用了 WebAssembly 的性能优势和 JavaScript 的灵活性。这个项目可以根据需求进一步扩展,增加更多的数学运算和高级功能。

Released under the MIT License.