【html】iOS26 液态玻璃实现效果

html 复制代码
<!DOCTYPE html>
<html lang="zh">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>液体玻璃效果演示</title>
		<style>
			body {
				margin: 0;
				overflow: hidden;
				background-color: #000;
			}
			canvas {
				display: block;
				width: 100vw;
				height: 100vh;
			}
			/* 控制面板 */
			#controls {
				position: absolute;
				top: 10px;
				left: 10px;
				padding: 10px;
				background-color: rgba(0, 0, 0, 0.5);
				color: white;
				font-family: sans-serif;
				border-radius: 5px;
				user-select: none;
			}
			#controls label {
				cursor: pointer;
			}
		</style>
	</head>
	<body>
		<canvas id="glslCanvas"></canvas>

		<img
			id="backgroundImage"
			src="https://pic2.zhimg.com/v2-a53c740141ab75eb4fe16af3ef8c35c5_r.jpg"
			crossorigin="anonymous"
			style="display: none"
		/>

		<div id="controls">
			<label>
				<input type="checkbox" id="useCircleCheckbox" />
				使用圆形
			</label>
		</div>

		<script id="vertex-shader" type="x-shader/x-vertex">
			// 将顶点位置传递出去,这样片段着色器就可以在构成平面的两个三角形上运行
			attribute vec2 a_position;
			void main() {
			    gl_Position = vec4(a_position, 0.0, 1.0);
			}
		</script>

		<script id="fragment-shader" type="x-shader/x-fragment">
			precision mediump float; // 为浮点数设置中等精度

			// 从 JavaScript 传入的变量
			uniform vec3 iResolution;
			uniform float iTime;
			uniform vec4 iMouse;
			uniform vec3 iImageResolution;
			uniform sampler2D iImage1;
			uniform float useCircle;

			// 已适配 WebGL ---
			vec2 R;
			const float PI = 3.14159265;

			// 创建旋转矩阵
			mat2 Rot(float a) {
			    float c = cos(a);
			    float s = sin(a);
			    return mat2(c, -s, s, c);
			}

			// 像素归一化处理
			float PX(float a) {
			    return a / R.y;
			}

			// 矩形距离场
			float Box(vec2 p, vec2 b) {
			    vec2 d = abs(p) - b;
			    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
			}

			// 圆形距离场
			float Circle(vec2 p, float r) {
			    return length(p) - r;
			}

			// 根据 useCircle 选择形状(圆形或圆角矩形)
			float Shape(vec2 p, vec2 b, float r) {
			    return useCircle > 0.5 ? Circle(p, r) : Box(p, b);
			}

			// 液体玻璃效果
			vec4 LiquidGlass(vec2 uv, float direction, float quality, float size) {
			    vec2 radius = size / R;
			    vec4 color = texture2D(iImage1, uv);

			    float d_step = PI / direction; // 方向步长
			    float i_step = 1.0 / quality;  // 质量步长
			    float d = 0.0;
			    // 循环被展开以兼容一些旧的GPU
			    for (int j = 0; j < 10; j++) {
			        if (float(j) >= direction) break;
			        float i = i_step;
			        for (int k = 0; k < 10; k++) {
			            if (float(k) >= quality) break;
			            color += texture2D(iImage1, uv + vec2(cos(d), sin(d)) * radius * i);
			            i += i_step;
			        }
			        d += d_step;
			    }

			    color /= quality * direction + 1.0; // 归一化
			    return color;
			}

			// 形状扭曲效果
			vec4 Distortion(vec2 uv) {
			    float shape = Shape(uv, vec2(PX(50.0)), PX(50.0));
			    float shapeShape = smoothstep(PX(1.5), 0.0, shape - PX(50.0)); // 形状平滑过渡
			    float shapeDisp = smoothstep(PX(75.0), 0.0, shape - PX(25.0)); // 边框宽度
			    float shapeLight = shapeShape * smoothstep(0.0, PX(20.0), shape - PX(40.0)); // 光照强度
			    return vec4(shapeShape, shapeDisp, shapeLight, 0.0);
			}

			void main() {
			    R = iResolution.xy;
			    vec2 uv = gl_FragCoord.xy / R; // 归一化UV坐标
			    vec2 st = (gl_FragCoord.xy - 0.5 * R) / R.y; // 屏幕空间坐标
			    vec2 M = iMouse.xy == vec2(0.0) ? vec2(0.0) : (iMouse.xy - 0.5 * R) / R.y; // 鼠标位置

			    // 如果鼠标没有移动过,则将效果定位在中心
			    if (iMouse.x == 0.0 && iMouse.y == 0.0) {
			         M = vec2(0.0);
			    }

			    vec4 dist = Distortion(st - M); // 计算扭曲效果

			    vec2 uv2 = uv;
			    uv2 *= 0.5 + 0.5 * smoothstep(0.5, 1.0, dist.y); // 缩放UV

			    vec3 col = mix(vec3(0.0), // 透明黑色背景
			                   0.2 + LiquidGlass(uv2, 10.0, 10.0, 5.0).rgb * 0.7, // 应用液体玻璃效果
			                   dist.x); // 根据图标形状混合
			    col += dist.z * 0.9 + dist.w; // 添加图标光照和图案

			    // 应用阴影效果
			    col *= 1.0 - 0.2 * smoothstep(PX(80.0), 0.0, Shape(st - M + vec2(0.0, PX(40.0)), vec2(PX(50.0)), PX(50.0)));

			    // 使用 dist.x 控制透明度:图标区域不透明,其他区域透明
			    float alpha = dist.x;

			    gl_FragColor = vec4(col, alpha); // 返回最终颜色和透明度
			}
		</script>

		<script>
			const canvas = document.getElementById('glslCanvas');
			const gl = canvas.getContext('webgl');
			if (!gl) {
				alert('抱歉,您的浏览器不支持 WebGL。');
			}

			// 获取 DOM 元素
			const backgroundImage = document.getElementById('backgroundImage');
			const useCircleCheckbox = document.getElementById('useCircleCheckbox');

			// 创建着色器程序
			function createShader(gl, type, source) {
				const shader = gl.createShader(type);
				gl.shaderSource(shader, source);
				gl.compileShader(shader);
				const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
				if (success) {
					return shader;
				}
				console.error(gl.getShaderInfoLog(shader));
				gl.deleteShader(shader);
			}

			const vertexShaderSource = document.getElementById('vertex-shader').text;
			const fragmentShaderSource =
				document.getElementById('fragment-shader').text;

			const vertexShader = createShader(
				gl,
				gl.VERTEX_SHADER,
				vertexShaderSource
			);
			const fragmentShader = createShader(
				gl,
				gl.FRAGMENT_SHADER,
				fragmentShaderSource
			);

			function createProgram(gl, vertexShader, fragmentShader) {
				const program = gl.createProgram();
				gl.attachShader(program, vertexShader);
				gl.attachShader(program, fragmentShader);
				gl.linkProgram(program);
				const success = gl.getProgramParameter(program, gl.LINK_STATUS);
				if (success) {
					return program;
				}
				console.error(gl.getProgramInfoLog(program));
				gl.deleteProgram(program);
			}

			const program = createProgram(gl, vertexShader, fragmentShader);
			gl.useProgram(program);

			// 准备数据 获取 uniform 和 attribute 的位置
			const positionAttributeLocation = gl.getAttribLocation(
				program,
				'a_position'
			);
			const resolutionUniformLocation = gl.getUniformLocation(
				program,
				'iResolution'
			);
			const timeUniformLocation = gl.getUniformLocation(program, 'iTime');
			const mouseUniformLocation = gl.getUniformLocation(program, 'iMouse');
			const imageResolutionUniformLocation = gl.getUniformLocation(
				program,
				'iImageResolution'
			);
			const imageSamplerUniformLocation = gl.getUniformLocation(
				program,
				'iImage1'
			);
			const useCircleUniformLocation = gl.getUniformLocation(
				program,
				'useCircle'
			);

			// 创建一个缓冲区来放置一个覆盖整个画布的矩形
			const positionBuffer = gl.createBuffer();
			gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
			const positions = [-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1];
			gl.bufferData(
				gl.ARRAY_BUFFER,
				new Float32Array(positions),
				gl.STATIC_DRAW
			);

			gl.enableVertexAttribArray(positionAttributeLocation);
			gl.vertexAttribPointer(
				positionAttributeLocation,
				2,
				gl.FLOAT,
				false,
				0,
				0
			);

			// 设置纹理
			let imageTexture;
			backgroundImage.onload = function () {
				imageTexture = gl.createTexture();
				gl.bindTexture(gl.TEXTURE_2D, imageTexture);
				// 设置纹理参数
				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
				// 将图片数据上传到纹理
				gl.texImage2D(
					gl.TEXTURE_2D,
					0,
					gl.RGBA,
					gl.RGBA,
					gl.UNSIGNED_BYTE,
					backgroundImage
				);

				// 设置图像分辨率 uniform
				gl.uniform3f(
					imageResolutionUniformLocation,
					backgroundImage.width,
					backgroundImage.height,
					0
				);

				// 启动渲染
				requestAnimationFrame(render);
			};
			// 如果图片已经加载完成 (例如从缓存加载)
			if (backgroundImage.complete) {
				backgroundImage.onload();
			}

			// 渲染循环
			let startTime = Date.now();
			let mouseX = 0;
			let mouseY = 0;

			function render(time) {
				// 调整画布大小以匹配显示大小
				const displayWidth = gl.canvas.clientWidth;
				const displayHeight = gl.canvas.clientHeight;
				if (
					gl.canvas.width !== displayWidth ||
					gl.canvas.height !== displayHeight
				) {
					gl.canvas.width = displayWidth;
					gl.canvas.height = displayHeight;
					gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
				}

				gl.clearColor(0, 0, 0, 0);
				gl.clear(gl.COLOR_BUFFER_BIT);

				// 更新 uniforms
				gl.uniform3f(
					resolutionUniformLocation,
					gl.canvas.width,
					gl.canvas.height,
					1.0
				);
				gl.uniform1f(timeUniformLocation, (Date.now() - startTime) * 0.001);
				gl.uniform4f(
					mouseUniformLocation,
					mouseX,
					gl.canvas.height - mouseY,
					0,
					0
				); // y坐标需要翻转
				gl.uniform1f(
					useCircleUniformLocation,
					useCircleCheckbox.checked ? 1.0 : 0.0
				);

				// 绑定纹理
				gl.activeTexture(gl.TEXTURE0);
				gl.bindTexture(gl.TEXTURE_2D, imageTexture);
				gl.uniform1i(imageSamplerUniformLocation, 0);

				// 绘制
				gl.drawArrays(gl.TRIANGLES, 0, 6);

				// 请求下一帧
				requestAnimationFrame(render);
			}

			// 事件监听
			window.addEventListener('mousemove', (e) => {
				mouseX = e.clientX;
				mouseY = e.clientY;
			});
		</script>
	</body>
</html>
相关推荐
不知火_caleb3 分钟前
前端应用更新提示的优雅实现:如何让用户及时刷新页面?
前端
前端小巷子4 分钟前
跨标签页通信(四):SharedWorker
前端·面试·浏览器
风铃喵游6 分钟前
平地起高楼: 环境搭建
前端·架构
昌平第一王昭君12 分钟前
基于antd pro封装的一个可拖动的modalform
前端
99乘法口诀万物皆可变15 分钟前
C#设计模式之AbstractFactory_抽象工厂_对象创建新模式-练习制作PANL(一)
服务器·javascript·c#·html
JiaLin_Denny27 分钟前
css 制作一个可以旋转的水泵效果
前端·css·动画·animation·transition
集成显卡38 分钟前
图片压缩工具 | Electron应用配合 commander 提供命令行调用功能
前端·javascript·electron·人机交互·命令行·cmd
我是来人间凑数的40 分钟前
electron 嵌入web网页的三种方式
前端·javascript·electron
GIS好难学1 小时前
Echarts数据可视化开发教程+120套开源数据可视化大屏H5模板
前端·信息可视化·echarts
OKUNP1 小时前
使用Haproxy搭建Web群集
前端