如何为容器添加WebGL悬浮效果并实现图片object-fit适配?
解决方案:为容器添加WebGL悬浮效果并实现
object-fit: cover适配 要实现目标效果,我们需要用WebGL渲染图片来实现悬浮变形,同时确保图片按object-fit: cover规则适配右侧52vw宽的容器。以下是完整实现步骤:
1. 调整基础布局与样式
先修正结构,隐藏原始图片,用WebGL画布承载渲染效果:
.casestart { height: 100vh; width: 100vw; position: relative; overflow: hidden; } .imageside { width: 52vw; height: 100vh; position: absolute; right: 0; background: #000; overflow: hidden; /* 限制内容不溢出容器 */ } canvas{ width: 100%; height: 100%; display: block; } #hoverImage { display: none; /* 仅作为WebGL纹理源,不显示 */ }
<div class="casestart"> <div class="imageside"> <canvas id="webglCanvas"></canvas> <img id="hoverImage" src="https://www.datocms-assets.com/92457/1676542990-mvsm_logitechquake2_explode01_v001_b.jpg?fit=max&fm=jpg&h=1920&w=1920" alt="" draggable="false"> </div> </div>
2. WebGL核心实现代码
以下JS代码实现鼠标悬浮变形效果,同时通过着色器计算实现object-fit: cover的适配逻辑:
const canvas = document.getElementById('webglCanvas'); const gl = canvas.getContext('webgl'); const image = document.getElementById('hoverImage'); // 适配容器尺寸 function resizeCanvas() { const container = document.querySelector('.imageside'); canvas.width = container.clientWidth; canvas.height = container.clientHeight; gl.viewport(0, 0, canvas.width, canvas.height); } window.addEventListener('resize', resizeCanvas); resizeCanvas(); // 顶点着色器 const vertexShaderSource = ` attribute vec2 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_texCoord = a_texCoord; } `; // 片段着色器:实现cover适配+悬浮变形 const fragmentShaderSource = ` precision mediump float; uniform sampler2D u_image; uniform vec2 u_resolution; uniform vec2 u_mouse; uniform float u_time; // 计算cover模式的纹理坐标 vec2 getCoverTexCoord(vec2 texCoord) { vec2 imageSize = vec2(textureSize(u_image, 0)); vec2 containerSize = u_resolution; float imageRatio = imageSize.x / imageSize.y; float containerRatio = containerSize.x / containerSize.y; vec2 scale = vec2(1.0); if (imageRatio > containerRatio) { scale.y = imageRatio / containerRatio; texCoord.y = (texCoord.y - 0.5) / scale.y + 0.5; } else { scale.x = containerRatio / imageRatio; texCoord.x = (texCoord.x - 0.5) / scale.x + 0.5; } return texCoord; } void main() { vec2 texCoord = getCoverTexCoord(v_texCoord); // 鼠标距离计算 vec2 mousePos = u_mouse / u_resolution; float distance = length(mousePos - texCoord); float strength = smoothstep(0.3, 0.0, distance); // 流动变形效果 vec2 offset = vec2( sin(texCoord.y * 10.0 + u_time) * strength * 0.02, cos(texCoord.x * 10.0 + u_time) * strength * 0.02 ); vec4 color = texture2D(u_image, texCoord + offset); gl_FragColor = color; } `; // 创建着色器工具函数 function createShader(gl, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error(gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } // 初始化WebGL程序 const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource); const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error(gl.getProgramInfoLog(program)); return; } gl.useProgram(program); // 设置顶点数据 const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); const positions = [ -1, -1, 1, -1, -1, 1, 1, 1, ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); const positionLocation = gl.getAttribLocation(program, 'a_position'); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); // 设置纹理坐标 const texCoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); const texCoords = [ 0, 0, 1, 0, 0, 1, 1, 1, ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW); const texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); gl.enableVertexAttribArray(texCoordLocation); gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); // 加载纹理 const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); 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); // 获取Uniform变量位置 const resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); const mouseLocation = gl.getUniformLocation(program, 'u_mouse'); const timeLocation = gl.getUniformLocation(program, 'u_time'); gl.uniform2f(resolutionLocation, canvas.width, canvas.height); // 跟踪鼠标位置 let mouseX = 0; let mouseY = 0; canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); mouseX = e.clientX - rect.left; mouseY = rect.bottom - e.clientY; // 适配WebGL左下角原点坐标 }); // 动画循环 function animate(time) { gl.uniform1f(timeLocation, time * 0.001); gl.uniform2f(mouseLocation, mouseX, mouseY); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); requestAnimationFrame(animate); } // 图片加载完成后启动动画 image.onload = () => { gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); animate(0); };
关键说明
object-fit: cover实现:在片段着色器中通过计算图片与容器的宽高比,动态调整纹理坐标,确保图片按cover规则填充容器,不会拉伸变形。- 悬浮变形效果:通过计算鼠标与纹理坐标的距离,生成动态偏移量,实现类似流动的变形效果,可调整
smoothstep参数和偏移量数值来控制效果强度。 - 响应式适配:监听窗口resize事件,实时调整画布尺寸和WebGL视口,保证容器大小变化时效果正常。
内容的提问来源于stack exchange,提问作者TOCJ
相关产品推荐
相关产品推荐

