Круглый прогресс-бар с эффектом движения воды

113
30 июня 2021, 03:50

Кто знает как можно реализовать такой круглый прогресс-бар с эффектом воды как в центральной части этого сайта https://asmobius.co.jp/

Answer 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; 
  } 
}
<canvas id="canvas" width=400 height=400></canvas>

READ ALSO
JSON в массив JS

JSON в массив JS

Имеется такой JSON:

84
Как сделать интерактивное расписание?

Как сделать интерактивное расписание?

Как сделать так что бы отображался только один день недели, и по нажатию менялся на другой? код

85
Видимость function declaration

Видимость function declaration

Функция объявленная как "function declaration" создаётся еще до выполнения кода, на стадии инициализацииЭто относится и к функциям в глобальном объекте...

99
Суммировать значения input

Суммировать значения input

Нужно сложить значения неизвестного кол-ва inputСумму вывести в другой input

106