整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

「译」使用.NET将WebAssembly扩展到云(

「译」使用.NET将WebAssembly扩展到云(一)

1线

/

DotNet NB 226

原文 | Richard Lander

翻译 | 郑子铭

WebAssembly(Wasm)是一种令人兴奋的新虚拟机和(汇编)指令格式。Wasm 诞生于浏览器,是 Blazor 项目的重要组成部分。Wasm 的第二个行动是针对应用程序和功能的云计算。WebAssembly 系统接口 (WASI) 是新的推动者,为 WebAssembly 代码提供了一种安全地跨语言调用和实现任意 API 的方法。现在可以使用 .NET 8 中的 wasi 实验工作负载通过 .NET 创建 WASI 应用程序。我们正在探索这些新技术并在此环境中运行 .NET 应用程序……。真的,任何地方。

这篇文章将帮助您了解 Wasm 的广泛使用,并描述 .NET 已经可以实现的功能。他们说历史不会重演,但会押韵。我们又回来进行另一轮“一次编写,随处运行”。WASI 应用程序是可移植的二进制文件,可以在任何硬件或操作系统上运行,并且不特定于任何编程语言。这一次,感觉不一样了。这不仅仅是供应商的神经;一切都是中立的。

Wasm 和 WASI

Wasm 可能会为我们提供云计算的重启,并承诺提供单一云原生二进制文件、更高的密度和更便宜的多租户。出于同样的原因,它也开启了边缘计算的可能性。事实上,CloudFlare 和 Fastly 已经使用 Wasm 在边缘托管公共计算。

Wasm 与在 Linux 容器中运行应用程序不同,后者是对现有标准和代码的(良好且聪明的)重新打包。Wasm 更像是在没有操作系统的环境中运行应用程序,只有汇编代码、内存和对外部世界的标准化(和门控)访问(通过 WASI)。

Build 2023 上的 Hyperlight 演示(4m 视频)深入了解了支持 Wasm 的云的外观。它演示了在新的轻量级安全虚拟机管理程序中运行的 Blazor 应用程序。Hyperlight 激发了新托管范例的想象力。

WebAssembly 系统接口 (WASI)、WebAssembly 接口类型 (WIT) 和 WebAssembly 组件模型是最新一轮 Wasm 创新的关键规范。它们基本上仍处于设计阶段并正在经历重大变化。这篇文章(以及 .NET 8 实现)以 WASI Preview 1 为中心。我们希望 .NET 9 实现使用 WASI Preview 2。

WIT 和 wit-bindgen 使用任何源语言编写的组件都可以与主机系统进行通信。WIT 对 C# 支持的实现由 @silesmo 领导。Wasm 和 WIT 一起定义了应用程序二进制接口(ABI)。

我们期望 WASI 成为一组标准的 WIT 类型,提供对低级功能的访问(例如获取时间和读取文件)。这些低级类型有效地形成了跨编程语言和操作系统的“Wasm 标准库”。例如,我们从来没有 Rust 开发人员和 .NET 开发人员可以同时使用的标准和共享功能。历史上还没有任何广泛部署的本机代码公开具有 OO 形状(如接口)的 API,可以跨编程语言和操作系统使用。

标准 WIT 类型以 wasi- 开头,定义“平台”。您可以将它们视为与 .NET 中的系统命名空间类似的方式(与 WASI 中的“S”匹配)。继续类比,您可以在 System 命名空间之外创建自己的 .NET 命名空间,WIT 也是如此。

这些帖子在更详细地构建 WASI 方面做得非常出色。

  • 标准化 WASI:在 Web 之外运行 WebAssembly 的系统接口

  • 宣布字节码联盟:为 WebAssembly 构建一个默认安全、可组合的未来

  • WebAssembly:开发人员更新的路线图

即将到来的承诺是能够采用现有的 .NET 应用程序或库并将其编译为 Wasm 目标。我们的设计本能是在 .NET 堆栈中实现相对较高的 WIT 接口(例如为 wasi-sql 创建 ADO.NET 数据提供程序),这将使现有代码(包括许多现有的 NuGet 包)能够正常工作,特别是对于没有本机依赖项。

Wasm 应用程序在 Wasm 运行时中运行,例如 wasmtime。与 Docker 非常相似,您可以使用特定功能配置该运行时。例如,如果您希望 Wasm 代码能够访问键/值存储,您可以向其公开一个键/值接口,该接口可以由本地数据库或云服务支持。

Wasm 运行时旨在可嵌入到应用程序中。事实上,有一个 wasmtime 包用于在 .NET 应用程序中托管 Wasm。.NET 代码可以作为 Wasm 运行,但 .NET 应用程序可以托管 wasmtime?!?是的,这个空间开始看起来是圆形的。虽然这些场景看起来很循环,但它们最终可能非常有用,与 AppDomain 的使用方式大致相似。这也让人想起所有“docker in docker”场景。
我们期待更多的创新、更多的 Wasm 运行时和更多的行业参与者。事实上,Wasm 已经升级为 W3C 规范。W3C 是 Wasm 的完美家园,让它成长为广泛的行业规范,就像之前的 HTML 和 XML 一样。

wasi-实验工作量

.NET 8 包含一个名为 wasi-experimental 的新工作负载。它构建在 Blazor 使用的 Wasm 功能之上,将其扩展为在 wasmtime 中运行并调用 WASI 接口。它还远未完成,但已经实现了有用的功能。

让我们从理论转向演示新功能。

安装 .NET 8 SDK 后,您可以安装 wasi-experimental 工作负载。

dotnet workload install wasi-experimental

注意:此命令可能需要管理员权限,例如在 Linux 和 macOS 上使用 sudo。

您还需要安装 wasmtime 来运行您即将生成的 Wasm 代码。

使用 wasi-console 模板尝试一个简单的示例。

$ dotnet new wasiconsole -o wasiconsole
$ cd wasiconsole
$ cat Program.cs
using System;

Console.WriteLine("Hello, WASI Console!");
$ dotnet run
WasmAppHost --runtime-config /Users/rich/wasiconsole/bin/Debug/net8.0/wasi-wasm/AppBundle/wasiconsole.runtimeconfig.json
Running: wasmtime run --dir . -- dotnet.wasm wasiconsole
Using working directory: /Users/rich/wasiconsole/bin/Debug/net8.0/wasi-wasm/AppBundle
Hello, WASI Console!

该应用程序使用 wasmtime 运行。这里没有 x64 或 Arm64,只有 Wasm。

dotnet run 提供额外的信息(在控制台输出中)来帮助解释发生了什么。未来这种情况可能会改变。与主机系统的所有交互均由 wasmtime 管理。

我们可以更深入地查看 AppBundle 目录。

$ ls -l bin/Release/net8.0/wasi-wasm/AppBundle
total 24872
-rwxr--r-- 1 rich staff 11191074 Oct 31 07:53 dotnet.wasm
-rwxr--r-- 1 rich staff 1526128 Oct 11 14:00 icudt.dat
drwxr-xr-x 6 rich staff 192 Nov 19 19:35 managed
-rwxr-xr-x 1 rich staff 48 Nov 19 19:35 run-wasmtime.sh
-rw-r--r-- 1 rich staff 915 Nov 19 19:35 runtimeconfig.bin
drwxr-xr-x 2 rich staff 64 Nov 19 19:35 tmp
-rw-r--r-- 1 rich staff 1457 Nov 19 19:35 wasiconsole.runtimeconfig.json
$ ls -l bin/Release/net8.0/wasi-wasm/AppBundle/managed
total 3432
-rw-r--r-- 1 rich staff 27136 Nov 19 19:35 System.Console.dll
-rw-r--r-- 1 rich staff 1711616 Nov 19 19:35 System.Private.CoreLib.dll
-rw-r--r-- 1 rich staff 5632 Nov 19 19:35 System.Runtime.dll
-rw-r--r-- 1 rich staff 5120 Nov 19 19:35 wasiconsole.dll

SDK 将应用程序发布到独立部署中。.NET 运行时 — dotnet.wasm — 已经编译为 Wasm(在我们的构建机器上)。应用程序和 dotnet.wasm 在 wasmtime 中一起加载,运行所有代码。应用程序的实际托管代码(位于托管目录中)在运行时解释,就像 Blazor WebAssembly 一样。 @yowl 和 @SingleAccretion 社区成员一直在尝试 Wasm 和原生 AOT。

您可能想知道为什么我们需要将所有这些文件分开,而显然更好的选择是拥有一个 wasiconsole.wasm 文件。我们也可以这样做,但稍后会在帖子中介绍它,因为我们需要在机器上安装更多的软件(目前 wasi 实验工作负载不包含这些软件)。

RuntimeInformation 告诉我们什么?

RuntimeInformation 是我最喜欢的类型之一。它让我们更好地了解目标环境。

我们可以稍微更改示例以显示一些更有用的信息。

using System;
using System.Runtime.InteropServices;

Console.WriteLine($"Hello {RuntimeInformation.OSDescription}:{RuntimeInformation.OSArchitecture}");
Console.WriteLine($"With love from {RuntimeInformation.FrameworkDescription}");

它产生这个输出。

Hello WASI:Wasm
With love from .NET 8.0.0

第一行很有趣。操作系统是WASI,架构是Wasm。这是有道理的,有更多的背景。文章前面提到 Wasm 可以被认为是“无操作系统”,但是我们不能简单地称之为 Wasm,因为现有的浏览器和 WASI 环境有很大不同。因此,该环境唯一一致的名称是 WASI,而 Wasm 明确是“芯片架构”。

Wasm 是一个 32 位计算环境,这意味着 2^32 字节是可寻址的。但是,Wasm 运行时可以配置为使用 memory64,从而可以访问 >4GB 的内存。我们还没有对此的支持。

访问主机文件系统

Wasmtime(和其他 Wasm 运行时)提供将主机目录映射到来宾目录的选项。从用户的角度来看,这与使用 Docker 进行卷挂载类似,但实现细节有所不同。

让我们看一个依赖目录安装的简单应用程序。它使用 Markdig 包将 markdown 转换为 HTML。公平地说,Markdig 并不是为了以 Wasm 的身份运行而编写的。只要能够为其创建一个舒适的管理环境,Markdig 就会很高兴,这就是我们所做的。

让我们在 Mac M1 (Arm64) 机器上尝试一下。

$ pwd
/Users/rich/git/wasm-samples/tomarkup
$ dotnet publish
$ cd bin/Release/net8.0/wasi-wasm/AppBundle
$ cat run-wasmtime.sh
wasmtime run --dir . dotnet.wasm tomarkup $*
$ ./run-wasmtime.sh
A valid inputfile must be provided.
$ wasmtime run --dir . --mapdir /markdown::/Users/rich/markdown --mapdir /tmp::/Users/rich dotnet.wasm tomarkup $* /markdown/README.md /tmp/README.html
$ ls ~/*.html
/Users/rich/README.html
$ cat ~/markdown/README.md | head -n 3
# .NET Runtime

[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status/dotnet/runtime/runtime?branchName=main)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=129&branchName=main)
$ cat ~/README.html | head -n 3
<h1>.NET Runtime</h1>
<p><a href="https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=129&amp;branchName=main"><img src="https://dev.azure.com/dnceng-public/public/_apis/build/status/dotnet/runtime/runtime?branchName=main" alt="Build Status" /></a>
<a href="https://github.com/dotnet/runtime/labels/help%20wanted"><img src="https://img.shields.io/github/issues/dotnet/runtime/help%20wanted?style=flat-square&amp;color=%232EA043&amp;label=help%20wanted" alt="Help Wanted" /></a>

--mapdir 正在挂载从主机到来宾的目录。

如您所见,Markdown 文件已转换为 HTML。为了简洁起见,显示了每个文件的前三行。

目录挂载所需的 CLI 手势目前有点不方便。这是我们需要在未来版本中考虑的内容。这实际上是一个 dotnet run 和 wasmtime run 应该如何关联的问题。

但它能算字数吗?

我最近出版了《System.IO 的便利》,重点关注字数统计。我们能否获得与 Wasm 相同的代码来运行并看看它的运行速度有多快?

该文章中的字数统计基准测试在 Linux x64 上运行。让我们保持不变,但这次以 Wasm 身份运行。

$ pwd
/Users/rich/git/convenience/wordcount/count
$ grep asm count.csproj
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
<WasmSingleFileBundle>true</WasmSingleFileBundle>
$ dotnet publish
$ cd bin/Release/net8.0/wasi-wasm/AppBundle/
$ WASMTIME_NEW_CLI=0 wasmtime run --mapdir /text::/home/rich/git/convenience/wordcount count.wasm $* /text/Clarissa_Harlowe
11716 110023 610515 /text/Clarissa_Harlowe/clarissa_volume1.txt
12124 110407 610557 /text/Clarissa_Harlowe/clarissa_volume2.txt
11961 109622 606948 /text/Clarissa_Harlowe/clarissa_volume3.txt
12168 111908 625888 /text/Clarissa_Harlowe/clarissa_volume4.txt
12626 108593 614062 /text/Clarissa_Harlowe/clarissa_volume5.txt
12434 107576 607619 /text/Clarissa_Harlowe/clarissa_volume6.txt
12818 112713 628322 /text/Clarissa_Harlowe/clarissa_volume7.txt
12331 109785 611792 /text/Clarissa_Harlowe/clarissa_volume8.txt
11771 104934 598265 /text/Clarissa_Harlowe/clarissa_volume9.txt
9 153 1044 /text/Clarissa_Harlowe/summary.md
109958 985714 5515012 total

我更新了项目文件以包含 wasi-wasmtrue 并注释掉 PublishAot 相关属性。我还添加了一个runtimeconfig.template.json 文件。未对应用程序代码进行任何更改。

现在,我们将整个应用程序放在一个文件包中。

$ ls -l bin/Release/net8.0/wasi-wasm/AppBundle/
total 6684
-rw-r--r-- 1 rich rich 1397 Nov 19 19:59 count.runtimeconfig.json
-rwxr-xr-x 1 rich rich 6827282 Nov 19 19:59 count.wasm
-rw-r--r-- 1 rich rich 915 Nov 19 19:59 runtimeconfig.bin
-rwxr-xr-x 1 rich rich 27 Nov 19 19:59 run-wasmtime.sh
drwxr-xr-x 2 rich rich 4096 Nov 19 19:59 tmp

看起来好多了。该应用程序只有不到 7MB。我必须安装 WASI-SDK 才能使用 WasmSingleFileBundle 属性并设置环境变量以使 dotnetpublish 能够找到所需的工具。

$ echo $WASI_SDK_PATH
/home/rich/wasi-sdk/wasi-sdk-20.0/

wasmtime 最近发生了重大变化。我选择使用 WASMTIME_NEW_CLI=0 来恢复运行示例的旧行为。

让我们回到性能。首先,作为 wasm 运行(通过解释器执行托管代码):

$ time WASMTIME_NEW_CLI=0 wasmtime run --mapdir /text::/home/rich/git/convenience/wordcount count.wasm $* /text/Clarissa_Harlowe
11716 110023 610515 /text/Clarissa_Harlowe/clarissa_volume1.txt
12124 110407 610557 /text/Clarissa_Harlowe/clarissa_volume2.txt
11961 109622 606948 /text/Clarissa_Harlowe/clarissa_volume3.txt
12168 111908 625888 /text/Clarissa_Harlowe/clarissa_volume4.txt
12626 108593 614062 /text/Clarissa_Harlowe/clarissa_volume5.txt
12434 107576 607619 /text/Clarissa_Harlowe/clarissa_volume6.txt
12818 112713 628322 /text/Clarissa_Harlowe/clarissa_volume7.txt
12331 109785 611792 /text/Clarissa_Harlowe/clarissa_volume8.txt
11771 104934 598265 /text/Clarissa_Harlowe/clarissa_volume9.txt
9 153 1044 /text/Clarissa_Harlowe/summary.md
109958 985714 5515012 total
Elapsed time (ms): 821
Elapsed time (us): 821223.8

real 0m0.897s
user 0m0.846s
sys 0m0.030s

现在有了我们对 Wasm 的(甚至更多)实验性原生 AOT 支持。

$ time WASMTIME_NEW_CLI=0 wasmtime run --mapdir /text::/home/rich/git/convenience/wordcount count.wasm $* /text/Clarissa_Harlowe
11716 110023 610515 /text/Clarissa_Harlowe/clarissa_volume1.txt
12124 110407 610557 /text/Clarissa_Harlowe/clarissa_volume2.txt
11961 109622 606948 /text/Clarissa_Harlowe/clarissa_volume3.txt
12168 111908 625888 /text/Clarissa_Harlowe/clarissa_volume4.txt
12626 108593 614062 /text/Clarissa_Harlowe/clarissa_volume5.txt
12434 107576 607619 /text/Clarissa_Harlowe/clarissa_volume6.txt
12818 112713 628322 /text/Clarissa_Harlowe/clarissa_volume7.txt
12331 109785 611792 /text/Clarissa_Harlowe/clarissa_volume8.txt
11771 104934 598265 /text/Clarissa_Harlowe/clarissa_volume9.txt
9 153 1044 /text/Clarissa_Harlowe/summary.md
109958 985714 5515012 total
Elapsed time (ms): 60
Elapsed time (us): 60322.2

real 0m0.107s
user 0m0.064s
sys 0m0.045s

现在,在 Linux x64 上使用 CoreCLR 运行:

$ time ./app/count ../Clarissa_Harlowe/
11716 110023 610515 ../Clarissa_Harlowe/clarissa_volume1.txt
12124 110407 610557 ../Clarissa_Harlowe/clarissa_volume2.txt
11961 109622 606948 ../Clarissa_Harlowe/clarissa_volume3.txt
12168 111908 625888 ../Clarissa_Harlowe/clarissa_volume4.txt
12626 108593 614062 ../Clarissa_Harlowe/clarissa_volume5.txt
12434 107576 607619 ../Clarissa_Harlowe/clarissa_volume6.txt
12818 112713 628322 ../Clarissa_Harlowe/clarissa_volume7.txt
12331 109785 611792 ../Clarissa_Harlowe/clarissa_volume8.txt
11771 104934 598265 ../Clarissa_Harlowe/clarissa_volume9.txt
9 153 1044 ../Clarissa_Harlowe/summary.md
109958 985714 5515012 total
Elapsed time (ms): 77
Elapsed time (us): 77252.9

real 0m0.128s
user 0m0.096s
sys 0m0.014s

这些都是有趣的结果。我们有解释、AOT 和 JIT 代码生成方法可供比较。Wasm 解释器能够在不到一秒的时间内计算(略低于)一百万个单词,而 AOT 编译的 Wasm 和 JIT 运行时可以在大约 100 毫秒内完成同样的操作。

注意:Main 方法是运行 main 的时间,由 StopWatch 测量。流程是整个流程的持续时间,以时间来衡量。

此图表显示了上下文中的所有结果,包括 System.IO 的便利性帖子中的结果。

wasmtime JIT 将 Wasm 代码编译到目标环境(在本例中为 Linux+x64)。例如,可以使用 wamr 对 Wasm 代码进行 AOT。我将把它留到另一篇文章中。

原文链接

Extending WebAssembly to the Cloud with .NET

家在做一些平面设计、文档编辑或网站页面的时候,经常会用到一些背景图片,但网上下载的背景图片有时不能满足实际需求,自己动手制作需要较高的美术功底,相关的制作软件也难以快速上手。其实借助一些云服务,通过简单设置就可以生成各种纹理图片、指定规格的站位图、有爆炸效果的图片以及纯CSS形态的背景图。

一、创建指定大小的占位图

我们在制作网页或者编辑文章的时候,经常都需要使用到占位图。这样可以在网页或文档制作完成后,根据实际的使用环境来替换需要的图片。那么如何快速制作出指定大小的占位图呢?

首先通过浏览器打开placy.org这个云服务,在打开页面的右侧就可以进行参数的设置。在PLACEHOLDER的区域里面,分别设置图片背景的宽度和高度,接下来在“Background color”参数中设置背景颜色的色号。然后可以根据自己的需要,来选择是否在图片中显示需要的文字,如果需要的话那么就要勾选上下面的TEXT选项。

首先在Caption选项里面输入需要的文字信息,文字信息输入完成后需要按下回车键,才可以在图片中进行显示。然后再根据提示,分别设置字体的类型、大小、形态和颜色等信息(图1)。所有的参数设置完成以后点击下面的“Get Placeholder”按钮,在弹出的对话框中就可以看到PNG、JPG 和 SVG三种不同图片格式的选项。


用户可以根据自己的需要进行选择,选择后点击“Download placeholder”按钮,就可以完成这个占位图的下载(图2)。另外,如果用户是进行网页设计,那么可以直接调用对话框中的代码信息,将其插入到自己编写的网页代码当中,从而可以非常方便的在网页中直接显示占位图。


二、创建波浪形的背景图片

现在有些手机的桌面是一种波浪效果的图片,看上去非常的不错。如果用户也想生成类似的图片,那么可以试一试“Wicked Backgrounds”这个生成器。通过它用户只需要在其中设定几个简单的参数,就可以立即生成需要的背景图片。首先通过浏览器打开它的官方网站wickedbackgrounds.com,点击网页中的“Launch Editor”按钮后就进入到生成器的编辑页面。该页面分为左右两个部分,在左侧主要是几个参数的设置区域,而右侧则是生成图片的预览区域。

首先点击左侧列表中的“Pick a color”选项,该服务默认使用的是深蓝色的主色调,点击该选项后在弹出的调色盘中根据自己的需要,利用鼠标点击来选择自己需要的主色调(图3)。点击“Angle”选项可以调整波浪的强度。点击“Color mode”可以选择图片的显示模式,默认选择的是Lighten浅色模式,用户可以根据需要选择深色模式或变色模式。点击“Contrast”选项可以调整波浪的不规则强度等等。


每当用户对一个选项进行操作后,在预览区域就可以实时显示出对应的效果。另外由于该服务默认是让图片呈横向显示,如果用户想将图片设置为手机桌面,那么可以点击预览区域上方的“Vertical Preview”选项,这样就可以将图片呈纵向显示。除此以外,点击“Full HD”选项还可以看到更多的尺寸参数,用户可以根据自己的需要来进行参数的选择(图4)。当所有设置完成后点击左侧列表最上方的“Downlaod”按钮,就可以完成图片的下载操作。


三、更加个性化的爆炸图片

如果用户觉得波浪图形的效果一般,那么还可以试试bbburst这款生成器。它可以快速生成具有爆炸效果的图片,不仅可以给用户一种更强烈的视觉效果,也更加适用于背景或者封面等环境。

首先通过浏览器打开它的官方网站fffuel.co/bbburst/,在生成器页面的opacity参数中,可以通过滑杆来调整图片的透明度参数。滑杆越左透明效果越差,而滑杆越右透明效果越好。接着在amount参数中设置图形的密度,同样滑杆往左密度越小滑杆往右密度越大。然后在下面有一个“blur edges”的选项,通过它可以设置图片不同区域的模糊效果。比如选择center选项就代表图片四周模糊而中间清晰,而选择“top left”选项就代表左侧清晰而右侧模糊(图5)。

接下来在网页下方可以看到“fill 1”、“fill 2”这样的列表,其中一个选项就代表一个图片中的图

案。在默认状态下生成器已经生成了五个不同的图案,用户可以通过生成器上方的预览图来进行查看。如果觉得不满意的话,可以点击图案后面的删除按钮,将默认的图案进行删除。然后再点击下面的加号按钮,这时生成器会自动创建一个颜色。当然用户也可以对颜色的色号进行修改,来设定自己需要的颜色,然后在右侧窗口中选择一个对应的图案即可(图6)。

所有的参数设置完成以后,点击页面右下角的randomize按钮,就会随机生成一个爆炸效果的图片。当用户通过预览图感觉满意后,点击右下角的Save按钮,就可以下载一个SVG格式的图片。当然也可以点击copy按钮,将其直接复制到系统的剪切板里面,这样就可以粘贴到其他的编辑器里进行二次加工。由于生成的是SVG格式的图片,即使是随意的进行放大操作,也不会出现任何模糊的情况。

关注我的头条号@爱玩软件的蓑草,了解最详细的电脑技巧!

前两天听歌的时候发现网易云的孤独星球动画挺好看,本想着在网上找找思路,谁知道直接就有,哈哈效果图(动态的,没找到动态工具,拿截图凑合看吧):

源代码

CSS

#effect-music {  position: relative;  margin: auto;  width: 100%;  height: 400px;  overflow: hidden;} #effect-music > .image {  position: absolute;  left: 0;  right: 0;  top: 0;  bottom: 0;  margin: auto;  width: 150px;  height: 150px; background: url(https://tvax1.sinaimg.cn/crop.0.280.720.720.180/cf2922d4ly1g4r5m8upm6j20k00zkqt7.jpg);  background-size: cover;  background-position: center center;  border-radius: 50%;  -webkit-border-radius: 50%;  -moz-border-radius: 50%;  -ms-border-radius: 50%;  -o-border-radius: 50%;  animation: rotate 10s linear 0s infinite;  -webkit-animation: rotate 10s linear 0s infinite;} #effect-music > .wave {  position: absolute;  opacity: 0;  left: 0;  right: 0;  top: 0;  bottom: 0;  margin: auto;  width: 204px;  height: 204px;  border-radius: 50%;  border: 2px solid #eee;  animation: wave 4s linear 0s infinite;  -webkit-animation: wave 4s linear 0s infinite;} #effect-music > .wave::after {  content: "";  position: absolute;  top: -4px;  left: 50%;  width: 6px;  height: 6px;  overflow: hidden;  border-radius: 5px;  -webkit-border-radius: 5px;  -moz-border-radius: 5px;  -ms-border-radius: 5px;  -o-border-radius: 5px;  background-color: #ccc;} #effect-music > .wave:nth-child(2) {  animation-delay: 1s;} #effect-music > .wave:nth-child(3) {  animation-delay: 2s;} #effect-music > .wave:nth-child(4) {  animation-delay: 3s;} @keyframes rotate {  from {    transform: rotate(0deg);    -webkit-transform: rotate(0deg);    -moz-transform: rotate(0deg);    -ms-transform: rotate(0deg);    -o-transform: rotate(0deg);  }  to {    transform: rotate(360deg);    -webkit-transform: rotate(360deg);    -moz-transform: rotate(360deg);    -ms-transform: rotate(360deg);    -o-transform: rotate(360deg);  }} @keyframes wave {  from {    opacity: 1;    transform: rotate(0deg) scale(1);    -webkit-transform: rotate(0deg) scale(1);    -moz-transform: rotate(0deg) scale(1);    -ms-transform: rotate(0deg) scale(1);    -o-transform: rotate(0deg) scale(1);  }  to {    opacity: 0;    transform: rotate(-300deg) scale(2.2);    -webkit-transform: rotate(-300deg) scale(2.2);    -moz-transform: rotate(-300deg) scale(2.2);    -ms-transform: rotate(-300deg) scale(2.2);    -o-transform: rotate(-300deg) scale(2.2);}}

HTML

<div id="effect-music"><div class="image"></div><div class="wave"></div><div class="wave"></div><div class="wave"></div><div class="wave"></div></div>