#javascript #canvas #progress_bar #webgl
Кто знает как можно реализовать такой круглый прогресс-бар с эффектом воды как в центральной части этого сайта https://asmobius.co.jp/
Ответы
Ответ 1
Подобные эффекты возможно сделать использовав фрагментный шейдер, в котором вычисляется каждый пиксель, в зависимости от положения слайдера. Здесь наблюдается 2 слоя: Один - 2 картинки одна поверх другой, между которыми происходит переход при помощи прозрачности картинки и сдвиг в противоположные стороны. Второй - кольцо, внутри которого так же находятся обе картинки, с границей, определяемой значением слайдера + искажение текстурных координат, для имитации эффекта воды. Для реализации я взял и скретил свои же старые ответы: эффект воды и огненное кольцо let pid, timeLocarion, valueLocation, v = Math.PI, value = Math.PI, gl = canvas.getContext('webgl'); loadTexture(0, "https://picsum.photos/id/88/400/400") loadTexture(1, "https://picsum.photos/id/77/400/400") addEventListener('wheel', s => { value = value - Math.sign(s.deltaY)*0.1; }) pid = gl.createProgram(); shader(`attribute vec2 c;void main(void){gl_Position=vec4(c,0.,1.);}`,gl.VERTEX_SHADER); shader(` precision lowp float; uniform vec2 size; // размер стороны квадрата этого беспредела uniform float time; // время uniform float val; // значение слайдера, от минус пи до пи uniform sampler2D tex1; // первая текстура uniform sampler2D tex2; // вторая ткестура // эффект воды vec2 waterEffect(vec2 uv) { float s = 0.; vec2 p = uv; for (int i = 0; i < 5; i++) { vec2 adjc = p; float theta = 2. * 3.1415 / 11.*float(i); adjc.x += cos(theta)*time*.1 + time * .03; adjc.y -= sin(theta)*time*.1 - time * .07; float a = adjc.x*cos(theta) - adjc.y*sin(theta); s += cos(a*15.) * 3.1415; } return p += sin(s)*.01; } void main(void) { // текстурные координаты vec2 uv = gl_FragCoord.xy / size.xy; // сдвигаем текстурные координаты, чтобы отсчет начинался от центра экрана vec2 c = uv - .5; // делаем из прямоугольника квадрат, чтобы был круг а не эллипс c.x *= size.x/size.y; // если фрагмент вне кольца if (abs(sqrt(dot(c, c)) - .4) > .05) { // смешиваем 2 текстуры в пропорциях в зависимости от положения слайдера float t = val/6.28 + .5; gl_FragColor = texture2D(tex1, vec2(uv.x + t*.1, uv.y))*t + texture2D(tex2, vec2(uv.x - t*.1, uv.y))*(1.-t); } else { // само кольцо vec2 water = waterEffect(uv); // тут небольшой хак с арктангенсом, обратите внимание на порядок следования /// аргументов, у нормального арктангенса первым аргументом идет y а вторым x, // перемена мест аргументов меняет местами оси системы координат - теперь //граница на кольце не слева а сверху if (atan(c.x, -c.y) > val) //справа или слева от границы ? gl_FragColor = texture2D(tex1, water); else gl_FragColor = texture2D(tex2, water); } } `, gl.FRAGMENT_SHADER); gl.linkProgram(pid); gl.useProgram(pid); gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,3,-1,-1,3,-1]), gl.STATIC_DRAW); let al = gl.getAttribLocation(pid, "c"); gl.vertexAttribPointer(al, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(al); timeLocation = gl.getUniformLocation(pid, 'time'); valueLocation = gl.getUniformLocation(pid, 'val'); let sizeLocation = gl.getUniformLocation(pid, 'size'); gl.uniform2f(sizeLocation, gl.drawingBufferWidth, gl.drawingBufferHeight) gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); requestAnimationFrame(draw) function loadTexture(i, url) { let loader = new Image(); loader.crossOrigin = "anonymous"; loader.src = url; loader.onload = function() { let texture = gl.createTexture(); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, loader); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 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.uniform1i(gl.getUniformLocation(pid, "tex"+i), i); gl.activeTexture(gl.TEXTURE1); } } function draw(t) { let speed = Math.max(0.0001, Math.abs(value-v)*0.1); if (v < value) v = Math.min(value, v+speed); if (v > value) v = Math.max(value, v-speed); let val = v; while (val < -Math.PI) val += Math.PI*2; while (val > Math.PI) val -= Math.PI*2; gl.clearColor(0, 0, 0, 0); gl.uniform1f(timeLocation, t / 1000); gl.uniform1f(valueLocation, val); gl.drawArrays(gl.TRIANGLES, 0, 3); requestAnimationFrame(draw) } function shader(src, type) { let sid = gl.createShader(type); gl.shaderSource(sid, src); gl.compileShader(sid); var message = gl.getShaderInfoLog(sid); gl.attachShader(pid, sid); if (message.length > 0) { console.log(src.split("\n").map((str, i) => ("" + (1 + i)) .padStart(4, "0") + ": " + str).join("\n")); throw message; } }
Комментариев нет:
Отправить комментарий