整合营销服务商

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

免费咨询热线:

使用 HTML5 Canvas 和 JavaScript 开发动画气泡:分步教程


家好! 欢迎来到本教程,我们将深入了解使用 HTML 画布和 JavaScript 在代码中创建有趣的气泡的世界。 最好的部分? 我们将只使用一点 HTML 和所有 JavaScript,而不是 CSS 来实现所有这一切。

揭示概念

今天,我们要掌握以下几个概念:

使用画布上下文的 arc 方法创建圆。

利用 requestAnimationFrame 函数实现平滑的圆形动画。

利用 JavaScript 类的强大功能来创建多个圆圈,而无需重复代码。

向我们的圆圈添加描边样式和填充样式以获得 3D 气泡效果。

你可以跟着我一起看,或者如果你想看源代码,可以使用最终的codepen

入门

首先,我们需要一个 HTML5 Canvas 元素。 Canvas 是创建形状、图像和图形的强大元素。 这就是气泡将产生的地方。 让我们来设置一下 -

<canvas id="canvas"></canvas>

为了使用画布做任何有意义的事情,我们需要访问它的上下文。 Context 提供了在画布上渲染对象和绘制形状的接口。

让我们访问画布及其上下文。

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

我们将设置画布以使用整个窗口的高度和宽度 -

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

让我们通过添加一些 css 为画布提供一个漂亮的舒缓浅蓝色背景。 这是我们要使用的唯一 CSS。 如果您愿意,也可以使用 JavaScript 来完成此操作。

#canvas {
  background: #00b4ff;
}

是时候创造泡泡了!

让我们进入有趣的部分。 我们将通过单击画布来创建气泡。 为了实现这一点,我们首先创建一个点击事件处理程序:

canvas.addEventListener('click', handleDrawCircle);

由于我们需要知道在画布上单击的位置,因此我们将在句柄 DrawCircle 函数中跟踪它并使用事件的坐标 -

//We are adding x and y here because we will need it later.
let x, y
const handleDrawCircle = (event) => {
  x = event.pageX;
  y = event.pageY;

// Draw a bubble!
  drawCircle(x, y);
};

用圆弧法画圆

为了创建圆圈,我们将利用画布上下文中可用的 arc 方法。 Arc 方法接受 x 和 y - 圆心、半径、起始角和结束角,对于我们来说,这将是 0 和 2* Math.PI,因为我们正在创建一个完整的圆。

const drawCircle = (x, y) => {
  context.beginPath();
  context.arc(x, y, 50, 0, 2 * Math.PI);
  context.strokeStyle = 'white';
  context.stroke();
};


使用 requestAnimationFrame 方法移动圆圈

现在我们有了圆圈,让我们让它们移动,因为……

GIF



请记住,当我们创建圆时,我们使用了 arc 方法,它接受 x 和 y 坐标 - 圆的中心。 如果我们快速移动圆的 x 和 y 坐标,就会给人一种圆在移动的印象。 让我们试试吧!

//Define a speed by which to increment to the x and y coordinates
const dx = Math.random() * 3;
const dy = Math.random() * 7;//Increment the center of the circle with this speed
x = x + dx;
y = y - dy;

我们可以将其移至函数内 -

let x, y;
const move = () => {
  const dx = Math.random() * 3;
  const dy = Math.random() * 7;  x = x + dx;
  y = y - dy;
};

为了让我们的圆圈无缝移动,我们将创建一个动画函数并使用浏览器的 requestAnimationFrame 方法来创建一个移动的圆圈。

const animate = () => {
  context.clearRect(0, 0, canvas.width, canvas.height);
  move();
    drawCircle(x,y);  requestAnimationFrame(animate);
};//Don't forget to call animate at the bottom 
animate();



创建粒子:引入粒子类

现在我们已经创建了一个圆圈,是时候创建多个圆圈了!

但在我们创建多个圆圈之前,让我们准备一下我们的代码。为了避免重复我们的代码,我们将使用类并引入 Particle 类。 粒子是我们动态艺术作品和动画的构建块。 每个气泡都是一个粒子,具有自己的位置、大小、运动和颜色属性。 让我们定义一个 Particle 类来封装这些属性:

class Particle {
  constructor(x = 0, y = 0) {}
  draw() {
    // Drawing the particle as a colored circle
    // ...
  }  move() {
    // Implementing particle movement
    // ...
  }
}

让我们将一些已设置的常量移至 Particle 类 -

class Particle {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
    this.radius = Math.random() * 50;
    this.dx = Math.random() * 3;
    this.dy = Math.random() * 7;
  }
  draw() {
    // Drawing the particle as a colored circle
    // ...
  }  move() {
    // Implementing particle movement
    // ...
  }
}

draw 方法将负责在画布上渲染粒子。 我们已经在drawCircle中实现了这个功能,所以让我们将它移动到我们的类中并将变量更新为类变量

class Particle {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
    this.radius = Math.random() * 50;
    this.dx = Math.random() * 3;
    this.dy = Math.random() * 7;
    this.color = 'white';
  }
  draw() {
    context.beginPath();
    context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
    context.strokeStyle = this.color;
    context.stroke();    context.fillStyle = this.color;
    context.fill();
  }  move() {}
}

同样,让我们在类中移动 move 函数 -

move() {
    this.x = this.x + this.dx;
    this.y = this.y - this.dy;
}

现在,我们需要确保在事件处理程序中调用 Particle 类。

const handleDrawCircle = (event) => {
  const x = event.pageX;
  const y = event.pageY;
  const particle = new Particle(x, y);
};canvas.addEventListener('click', handleDrawCircle);

由于我们需要在 animate 函数中访问该粒子,以便调用其 move 方法,因此我们将该粒子存储在一个名为 molecularArray 的数组中。 当创建大量粒子时,这个数组也会很有帮助。 这是反映这一点的更新代码 -

const particleArray = [];
const handleDrawCircle = (event) => {
  const x = event.pageX;
  const y = event.pageY;  const particle = new Particle(x, y);
  particleArray.push(particle);
};canvas.addEventListener('click', handleDrawCircle);

记得也要更新动画功能 -

此时,您将在屏幕上看到这个粒子 -



惊人的! 现在,到了有趣的部分! 让我们创建很多圆圈并设计它们的样式,使它们看起来像气泡。

为了创建大量气泡,我们将使用 for 循环创建粒子并将它们添加到我们在此处创建的粒子数组中。

const handleDrawCircle = (event) => {
  const x = event.pageX;
  const y = event.pageY;
  for (let i = 0; i < 50; i++) {
    const particle = new Particle(x, y);
    particleArray.push(particle);
  }
};canvas.addEventListener('click', handleDrawCircle);

在动画函数中,我们将通过清除画布并在新位置重新绘制粒子来不断更新画布。 这会给人一种圆圈在移动的错觉。

const animate = () => {
  context.clearRect(0, 0, canvas.width, canvas.height);
  particleArray.forEach((particle) => {
    particle?.move();
    particle?.draw();
  });  requestAnimationFrame(animate);
};animate();


现在我们有了移动的气泡,是时候给它们添加颜色,使它们看起来像气泡了!

我们将通过向气泡添加渐变填充来实现此目的。 这可以使用 context.createRadialGradient 方法来完成。

const gradient = context.createRadialGradient(
  this.x,
  this.y,
  1,
  this.x + 0.5,
  this.y + 0.5,
  this.radius
);
gradient.addColorStop(0.3, 'rgba(255, 255, 255, 0.3)');
gradient.addColorStop(0.95, '#e7feff');context.fillStyle = gradient;



总结

恭喜! 您刚刚仅使用 HTML Canvas 和 JavaScript 创建了一些超级有趣的东西。 您已经学习了如何使用 arc 方法、利用 requestAnimationFrame、利用 JavaScript 类的强大功能以及使用渐变设计气泡以实现 3D 气泡效果。

请随意尝试颜色、速度和大小,使您的动画真正独一无二。

请随意尝试颜色、速度和大小,使您的动画真正独一无二。

我希望您在学习本教程时能像我在创建它时一样获得乐趣。 现在,轮到你进行实验了。 我很想看看你是否尝试过这个以及你创造了什么。 与我分享您的代码链接,我很乐意查看。

代浏览器都内置了专用动画技术,Martin Görner为您展现四种最棒的实例。

现代移动操作系统将用户接口动画化,并已达到了电脑端交互的标准——精选流畅的动画,体现出沉浸效果以及支持直观的交互。我们都想当然的认为,可以设置一个列表,使之带有运动特性,用手指轻轻一推,它就如同有重量和惯性一样运动起来,直到遇到边缘再反弹回来一点。但是,在网络上,我们还达不到这样的效果。

接受事实吧,现有网上的动画经常被视作UI灾难,还在使用二十年前陈旧的<blink>标签技术。加入Flash有点帮助,不过它缺乏HTML DOM集成,都变成了不美观的800×600分辨率,并且JavaScript DOM操作还有其标志性的5帧/秒(fps)动画率——真是绝望啊!

改变

2013年,现代浏览器内置了专用动画技术,达到60 fps。是时候去忘掉过去,开始构建美妙的UI动画体验了。我将展示四个动画技术,帮助你决定,哪一个更适合你的项目。

CSS3动画——3D

让我们从最简单的声明式技术开始:CSS3。这里不需要JavaScript,存CSS,加上一点现代手段。

CSS通过两种基本属性来声明动画:过渡和动画。过渡属性通知浏览器计算两种状态(由各自CSS定义)间的中间帧。动画通过改变元素CSS触发。比如,当你以编程方式改变它的层,或启动一个:hover CSS。

当缩略图层从开始转变到结束,图像则从一处平滑的移动到另一处,典型的表现为JavaScript DOM动作的结果。

img { -webkit-transition: 1s; }.begin { left: 0px; }.end { left: 500px; }

动画属性最常用于持续运行的动画特效,它还允许自定义中间步骤的动画。

创建一个旋转的图像:

img {-webkit-animation: myAnim 3s infinite; }@-webkit-keyframes myAnim{ from {-webkit-transform: rotate(0deg) } 50% {/* possible intermediate positions at any % */} to {-webkit-transform: rotate(360deg) }}

利用大量CSS属性制作动画具有无限的创造性,你可以根据意图在边框宽度内任意创建动画。 不过,大多数对动画有用的属性都是几何变换和不透明的。CSS3通过易操作的转换属性提供了全系列二维几何变换:平移、旋转、缩放和歪斜。

用旋转、缩放和歪斜创建2D转换:

  • webkit-transform:旋转30度

  • webkit-transform:缩小50%

  • webkit-transform:歪斜-20度,再20度

接下来变得有趣了。如果你觉得在CSS中添加3D效果,技术上是为了粗体和斜体的设计,这很愚蠢,请接着阅读。通过扩展几何转换到三维图像将是一个很自然的方式,相同变换属性还可以做平移X,Y,Z轴以及旋转X,Y,Z轴。

3D旋转图像:

  • webkit-transform:Y轴旋转45度

再加上点抛出动画效果,看上去就像是一个旋转的三维立方体。

如果确实这般容易的话,就不会有任何趣味性了。设计师留了一点悬念,你可以试一下,只对一个图像进行旋转,浏览器默认渲染出来的效果是不带透视性的:

  • webkit-transform:Y轴旋转45度

可以添加上透明属性,默认是无穷大极限值也是不带有一点透明效果的。就如同靠近镜头的对象与很远距离外的对象看上去大小一样。

为了确保图像看上去更真实,你还需要指定一个镜头与屏幕之间距离的值。加上-webkitperspective: 1000px就可以了。

一点数学问题:什么是透视性?

要在一块平面屏幕上呈现一个3D对象,首先画出一条线连接眼镜或相机与3D物理上的点。这条线与屏幕的交集就是相应像素所在的位置。为了找到像素在屏幕上的坐标,利用泰勒斯定理,你需要空间中点的3D坐标和相机与屏幕之间的距离(f在光学中也被称为焦距)。这就是视角属性提供的距离。如果这个距离是无限远的,你可以看到所有射线(眼球到3D点线)是水平紧密状的,而且很远很远的对象在屏幕上的大小一样。

现在我们有设置一个3D立方体的所有基础了,利用六个图像以及CSS 3D转换,接下来就可以实现旋转了。

旋转

这一步很简单:我们把一个三维旋转封装到一个div,将会产生如下效果:

浏览器执行你让它所做的步骤:在div渲染一个三维立方体,然后像一张平面图片般在自身运用3D旋转。这是默认效果,虽然不是你想要的。你要告诉浏览器运用嵌套div来构成3D转换,-webkit-transform-style: preserve-3d属性。通过这个,我们就能得到想要的旋转立方体了。

准确获取

很容易在空间失去定位。我的建议是采用一个div,命名为SCENE。这就是透明属性存放的位置。在内部,放置一个命名为OBJECT的div,它应当拥有transform-style: preserve-3d属性,并且该div就是你应用转换移动整个对象的地方。最后,在该div内部,使用3D转换设置你想要的正面图像,创建所需对象。在我们的例子中,立方体的六个面:

.SCENE { -webkit-perspective: 1000px; }.OBJECT{ -webkit-transform-style: preserve-3d; -webkit-transform: rotateX(20deg rotateY(120deg) rotateZ(50deg); /* replace this with an animation property if you want movement */}.FACE1 { -webkit-transform:translateX(150px)rotateY(90deg);}...

关于3D CSS转换很棒的一点是CSS动画和过渡的完美结合。我们在这留了一个练习,让旋转立方体运动起来,当鼠标光标悬停其上时,立方体自动打开来。操作方法是:把立方体的面移动到与中心一定距离的地方。第二组CSS属性有一个 :hover选择器,将立方体的面放置在更远的距离。通过运用第一种转换属性,你将看到立方体从中心盘旋打开,同时还保持旋转(演示)。

可缩放的矢量图像(SVG)

HTML与CSS都是强大的动画技术,但缺乏绘图基元。SVG能够弥补这点,并拥有其独特的动画标签。SVG动画部分被特定称为同步多媒体集成语言(SMIL)。

首先,SVG是用于矢量基元的,比如矩形、圆形和贝塞尔曲线:

<svg> <rect x="5" y="5" width="140" height="140" stroke="#000000" strokewidth="4"fill="#AAAAFF" opacity="1"/></svg>

同样可用于:

<line x1 y1 x2 y2><circle cx cy r><path d><image x y width height xlink:href>

其中一个基元,<path>标签,是SVG中最好用的。它允许你使用直线、弧和贝塞尔曲线定义任意路径。路径定义看上去想一个字母,如同Inkscape矢量图形软件生成的。对于SVG动画,你应该理解这一点。

一个二次和三次贝塞尔曲线的例子:

<svg> <path d="M 30,240 Q 170,40 260,230" stroke="#F00" /> <path d="M 30,240 C 70,90 210,150 260,230" stroke="#F00" /></svg>

语法:

M x,y     新的起点 (标记)

L x,y      到哪里的直线

Q cx, cy, x, y  二次贝塞尔曲线到 (x,y)和一个控制点

C cx,cy, dx,dy, x,y 三次贝塞尔曲线到(x, y)和两个控制点

A       椭圆弧

z       字符串最后,用于结束路径

让我们把这些矢量变得更生动化。你可以查看演示,一个家伙踏着滑板翱翔于白云间。

冲浪板上下摆动,小人的嘴巴在大和更大的笑容间交替变化,眼睛滚动,瞳孔扩张。这是SVG动画四种可能的类型。

最简单的一种SVG动画运用<animate>标签,改变一种几何形状的一个参数,在本例中,就是眼睛的半径。

要使瞳孔扩张,需要改变属性列表值中的半径值。

<circle cx="200" cy="205" r="80" > <animate dur="3s" attributeName="r" values="80; 150; 80" repeatCount="indefinite" /></circle>

方便的是变化的属性还可以成为<path>标签。允许你创建一个动画路径。 唯一的限制是两个曲线之间要进行转换,必须是同一种类型且拥有相同数量的控制点。它们必须由相同位置上的同一个字母定义,唯一不同的只能是参数的改变。当移动小人的嘴巴时,只有“微笑”和“大笑”位置被定义了。SVG动画将完成插值。

<path fill="#fff"> <animate attributeName="d" dur="2s" repeatCount="indefinite" values="m 0,0 c 1,15 -13,45 -45,45 -32,0 -44,-28 -44,-44 z; m 0,0 c -4,15 -66,106 -98,106 -32,0 3,-89 9,-105 z" /></path>

当然,SVG还能进行几何变换,也能做成动画。这里的动画标签是<animate Transform>。你必须告诉它想要将哪里的转变做成动画,还要提供一个分号分隔的所有关键位置的值列表。还可以组成动画转换,你告诉浏览器使用additive=”sum” 属性。

几何转换的动画:

<g> ... <!-- SVG primitives group --> <animateTransform dur="3s" repeatCount="indefinite" additive="sum" attributeName="transform" type="translate" values="0,0; 200,-130; -100,200; 0,0" /> <animateTransform dur="3s" repeatCount="indefinite" additive="sum" attributeName="transform" type="rotate" values="0; 20; -20; 0" /></g>

第三个和最后一个SVG动画标签也很有用。<animateMotion>用来引导对象沿着特定路径运动。 它有一个隐藏的技巧称为rotate=”auto”属性。它让对象不仅能够遵循指定路径,同时自身保持朝前,如同路上行驶的车辆一样。

<g> ... <!-- SVG primitives group --> <animateMotion dur=”1s"repeatCount="indefinite" path="m 0,0 a 15,11 0 1 1 -30,0 15,11 0 1 1 30,0 z" /></g>

SMIL有大量控制动画特征。在它的JavaScript API中揭露pauseAnimations(), unpauseAnimations()和setCurrentTime(t) 函数在全局性开始/停止/暂停一个动画的功能。还在所有三种动画标签(<animate>, <animateTransform>, <animateMotion>)上指定了begin与end属性。它们可以利用一个事件节点/用户事件/动画事件的强大组合。你可以指定,动画在点击之后的一秒结束或开始。

例如,下面这个SVG按钮被点击后有一个移动的阴影效果:

<g id="buttonID"> <!-- SVG button artwork here --> <animate begin="buttonID.click" dur="1s"\ .../></g>

HTML5绘图

由声明性动画转换到编程性动画技术,<canvas>标签是你的第一选择。本文所提到的所有技术中,<canvas>标签最具有跨浏览器支持,浏览器厂商做了很多努力使之兼容60fps动画,以下是设置一个画布的方法:

<canvas width="400" height="400" style = "width: 400px; height: 400px;"></canvas>

第一个尺寸(标签属性)设置了画布的分辨率,这就是你所使用的坐标空间。第二个尺寸(CSS属性)是画布出现在屏幕上的形状大小。为什么不把画布物理尺寸设置到100%分辨率,内部坐标空间不变,浏览器为任意窗口都能正确渲染图画?非常遗憾,浏览器将画布内容作为点阵图来扫描的,任何扩大范围的结果都将导致模糊混乱的像素。于是,把这两个值设置成相同的值是唯一可行的选项。

初始化画布需要JavaScript中<canvas>元素,并在其上调用getContext('2d')函数。获得的图画内容对象被用来调用到画布API。内容是状态性的,存储三种不同的状态信息:绘画风格,即时几何转换以及累计绘图路径。在画布上进行绘画的方法是发布绘图指令,不产生任何可见内容,但在内存中已创建了一个路径,然后发布一个油墨指令(ctx.stroke(), ctx.fill() 或两者皆有),就能显示出路径了。

逐帧动画

画布动画需要设置一个动画循环,你应当基于requestAnimationFrame()函数(带有正确的前缀)。该函数使浏览器能够管理帧率,在浏览器选项卡隐藏时也能停止动画。

function runAnimation(){ yourWorld.draw() // this is your drawing code webkitRequestAnimationFrame(runAnimation);}

一般来说,画布被用于动画的时候,对象的位置必须是在逐帧的基础上才行,这是因为它们都是物理仿真的结果。观看box2dweb.js教程。仿真不断改变仿真世界中对象的位置,动画循环周期性的在屏幕上展现当前环境状态。

画布SVG精灵

在画布动画中使用SVG精灵是一种很有用的技巧。一个任意复杂性的静态矢量图片在画布上呈现都是繁琐的,这样做实际上可行,虽然不如发送一个.svg到 ctx.drawImage()简单。出于模糊安全的原因,只有内联SVG支持,你必须在Blob API中使用它(代码)。

SVG精灵能够以任意比例展示其矢量美态,但遗憾的是,只支持Chrome浏览器。

使用WebGL

WebGL暴露浏览器运行的JavaScript应用程序OpenGL API,而不需要任何插件。理论上讲,你只需要在<canvas> 元素上调用getContext(‘webgl’)(而不是之前的2d)就可以了。然而,WebGL非常大,并且API等级低。最重要的是,web版本没有默认的渲染路径,这意味着你无法给它一个形状,让它使用适合的默认值展示出来。所以你必须编写着色器,使用GLSL语言编写屏幕上出现的任何内容。

幸运的是我们有Three.js帮助。Three最初是由Mr.doob开发的库,用于一些Chrome演示(www.ro.me/www.chaostoperfection.com),但它是相对独立的。它拥有所有的基础,以及内置独特的着色器,你就可以专注于其他有用的方面:相机、灯光、行动。

首先要做的是编写Three粘合代码(很简单):

var renderer = new THREE.WebGLRenderer({antialias: true});renderer.setSize(width, height);renderer.setClearColorHex(0x000000, 0); // color,transparency// the renderer creates a canvas elementfor youdocument.whereeveryouwant.appendChild(renderer.domElement);

在lights和action之前,需创建camera和位置:

// arguments: FOV,viewAspectRatio, zNear, zFarvar camera = new THREE.PerspectiveCamera(35,width/height, 1, 10000);camera.position.z = 300;

然后是light:

var light = new THREE.DirectionalLight(0xffffff, 1);//color, intensitylight.position.set(1, 1, 0.3); // direction

我们试着呈现一个立方体吧,3D对象在Three中被称为网络,都由一个几何体和一个材料构成。

我们使用一个简单的结构:

var texture = THREE.ImageUtils.loadTexture(‘Fernando Togni.jpg’);var cube = new THREE.Mesh( new THREE.CubeGeometry(100, 100, 100), new THREE.MeshLambertMaterial({map: texture}) );

最后添加内容,称为渲染函数。

var scene = new THREE.Scene();scene.add(cube);scene.add(light);renderer.render(scene, camera);

这将为我们的立方体产生一个静态图像,为了让它动起来,我们包裹渲染召集一个动画循环,改变立方体在每一帧的位置。

function runAnimation(t){ // animate your objects depending on time cube.rotation.y = t/1000; cube.position.x = ... renderer.render(scene, camera); // display requestAnimationFrame(runAnimation); // and loop}

推荐使用三维建模软件例如Sketchup。Three识别几类3D模型结构,包括COLLADA(.dae)具有广泛行业支撑。下面是如何在Three中加载一个模型:

var loader = new THREE.ColladaLoader();loader.load("Android.dae", function(collada){ var model = collada.scene; model.position = ...; // x, y, z model.rotation = ...; // x, y, z scene.add(model);} );

再做一些努力,更换机器人外观花式,Three提供兰伯特和冯氏照明风格,法线贴图,凹凸贴图,环境映射以及更多。

还可以使机器人运动起来,具体步骤参考教程。

原文链接:http://www.gbtags.com/gb/share/2124.htm

tml:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE-edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>玻璃效果网站</title>
        <link href="../css/20240302.css" rel="stylesheet">
    </head>
    <body>
        <main>
            <section class="glass">
                <div class="dashboard">
                    <div class="user">
                        <img src="./image/20240302/photo1.png" alt="">
                        <h3>
                            Demo
                        </h3>
                        <p>Pro Member</p>
                    </div>
                    <div class="links">
                        <div class="link">
                            <img src="./image/20240302/Streams.png" alt="" />
                            <h2>Streams</h2>
                        </div>
                        <div class="link">
                            <img src="./image/20240302/Games.png" alt="" />
                            <h2>Games</h2>
                        </div>
                        <div class="link">
                            <img src="./image/20240302/news.png" alt="" />
                            <h2>New</h2>
                        </div>
                        <div class="link">
                            <img src="./image/20240302/Library.png" alt="" />
                            <h2>Library</h2>
                        </div>
                    </div>
                    <div class="pro">
                        <h2>Join pro for free games.</h2>
                        <img src="./image/20240302/show_128.png" alt="" />
                    </div>
                </div>
                <div class="games">
                    <div class="status">
                        <h1>Active Games</h1>
                        <input type="text" />
                    </div>
                    <div class="cards">
                        <div class="card">
                            <img src="./image/20240302/20240302_1.png" alt="" />
                            <div class="card-info">
                                <h2>恶 魔 之 魂</h2>
                                <p>PS5 Version</p>
                                <div class="progress"></div>
                            </div>
                            <h2 class="percentage">60%</h2>
                        </div>
                        <div class="card">
                            <img src="./image/20240302/20240302_2.png" alt="" />
                            <div class="card-info">
                                <h2>无 路 之 旅</h2>
                                <p>PS5 Version</p>
                                <div class="progress"></div>
                            </div>
                            <h2 class="percentage">60%</h2>
                        </div>
                        <div class="card">
                            <img src="./image/20240302/20240302_3.png" alt="" />
                            <div class="card-info">
                                <h2>麻布仔大冒险</h2>
                                <p>PS5 Version</p>
                                <div class="progress"></div>
                            </div>
                            <h2 class="percentage">60%</h2>
                        </div>
                    </div>
                </div>
            </section>
        </main>
        <div class="circle1"></div>
        <div class="circle2"></div>
    </body>
</html>

Css:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

h1{
    color: #426696;
    font-weight: 600;
    font-size: 3rem;
    opacity: 0.8;
}

h2, 
p{
    color: #658ec6;
    font-weight: 500;
    opacity: 0.8;
}

h3{
    color: #426696;
    font-weight: 600;
    opacity: 0.8;
}

main {
    font-family: "Poppins", sans-serif;
    min-height: 100vh;
    background: linear-gradient(to right top, #ffcb06, #ec9c07);
    display: flex;
    align-items: center;
    justify-content: center;
}

.glass{
    background: white;
    min-height: 80vh;
    width: 60%;
    background: linear-gradient(
        to right bottom, 
        rgba(255, 255, 255, 0.7), 
        rgba(255, 255, 255, 0.3)
    );
    border-radius: 2rem;
    z-index: 2;
    backdrop-filter: blur(2rem);
    display: flex;
}

.circle1,.circle2{
    background: white;
    background: linear-gradient(
        to right bottom, 
        rgba(255, 255, 255, 0.8), 
        rgba(255, 255, 255, 0.3)
    );
    height: 20rem;
    width: 20rem;
    position: absolute;
    border-radius: 50%;
}

.circle1{
    top: 5%;
    right: 15%;
}

.circle2{
    bottom: 5%;
    left: 10%;
}

.dashboard{
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-evenly;
    text-align: center;
    background: linear-gradient(
        to right bottom, 
        rgba(255, 255, 255, 0.7), 
        rgba(255, 255, 255, 0.3)
    );
    border-radius: 2rem;
}

.link{
    display: flex;
    margin: 2rem 0rem;
    padding: 1rem 5rem;
    align-items: center;
}

.link h2{
    padding: 0rem 1rem;
}

.games{
    flex: 2;
}

.pro{
    background: linear-gradient(to right top, #ffcb06, #ec9c07);
    border-radius: 2rem;
    color: white;
    padding: 1rem;
    position: relative;
}

.pro img{
    position: absolute;
    top: 5%;
    right: 8%;
}

.pro h2{
    width: 40%;
    color: white;
    font-weight: 600;
}

.status{
    margin-bottom: 3rem;

}

.status input{
    background: linear-gradient(
        to right bottom, 
        rgba(255, 255, 255, 0.7), 
        rgba(255, 255, 255, 0.3)
    );
    border: none;
    width: 50%;
    padding: 0.5rem;
    border-radius: 2rem;
}

.games{
    margin: 5rem;
    display: flex;
    flex-direction: column;
    justify-content: space-evenly;
}

.card{
    display: flex;
    background: linear-gradient(
        to left top, 
        rgba(255, 255, 255, 0.8), 
        rgba(255, 255, 255, 0.5)
    );
    border-radius: 1rem;
    margin: 2rem 0rem;
    padding: 2rem;
    box-shadow: 6px 6px 20px rgba(122, 122, 122, 0.212);
    justify-content: space-between;
}

.card-info{
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.progress{
    background: linear-gradient(to right top, #ffcb06, #ec9c07);
    width: 100%;
    height: 25%;
    border-radius: 1rem;
    position: relative;
    overflow: hidden;
}

.progress::after {
    content: "";
    width: 100%;
    height: 100%;
    background: rgba(236, 236, 236);
    position: absolute;
    left: 60%;
}

.percentage{
    font-weight: bold;
    background: linear-gradient(to right top, #ffcb06, #ec9c07);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}


效果