2 957 Codepen

Глубокий фрактальный зум

Три разновидности фрактала. При перемещении курсора мы либо погружаемся, либо всплываем.

HTML

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Deep Fractal Zoom</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- partial:index.partial.html -->
  <div class="wrapper">
    <canvas id="c"></canvas>
  </div>

<div class="choices">
  <button data-url="https://s3misc20190920.s3.amazonaws.com/3434441128_1430362800_o.png">1</button>
  <button data-url="https://s3misc20190920.s3.amazonaws.com/3433634531_c14662a9f2_o.png">2</button>
  <button data-url="https://s3misc20190920.s3.amazonaws.com/3433654007_d0ed0585db_o.png">3</button>
</div>

<script id="vertex-shader" type="x-shader/x-vertex">

attribute vec2 a_coords;
varying vec2 v_c;

void main() {
  gl_Position = vec4(a_coords * vec2(1, -1), 0, 1);
  v_c = a_coords;
}

</script>

<script id="fragment-shader" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif

uniform sampler2D u_image;
uniform float u_scale;
uniform float u_zoom;
varying vec2 v_c;

void main() {

  gl_FragColor = texture2D(u_image, vec2(
    (0.5 + atan(v_c.y, v_c.x) / 6.2831853), -u_scale * log2(length(v_c) * u_zoom)
  ));

}

</script>
<!-- partial -->
  <script  src="./script.js"></script>

</body>
</html>

CSS

* {
    margin: 0;
    padding: 0;
    overflow: hidden;
}

html, body {
  background: black;
  width: 100%;
  height: 100%;
    overflow: hidden;
}

.wrapper {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

canvas {
    display: block;
  position: absolute;
}

.choices {
    position: absolute;
    top: 0;
    left: 0;
}

.choices button {
    display: inline-block;
    width: 40px;
    height: 40px;
    border: none;
    background-color: white;
    font-size: 15px;
    font-weight: bold;
}

.choices button:hover {
    background-color: yellow;
    cursor: pointer;
}

JS

var image,
    canvas,
    tex,
    ctx,
    rect,
    uscale,
    uzoom,
    zoom,
    gl,
    iW,
    iH,
    init = true,
    touched = false,
    timeout,
    m = [0, 0],
    slowX = 0;

window.onload = start;
window.onresize = resize;

window.addEventListener('mousemove', mouse);
window.addEventListener('touchmove', mouse);

function resize() {

  iW = document.documentElement.clientWidth;
  iH = document.documentElement.clientHeight;

  let sq = iW > iH ? iW : iH;

  canvas.width = canvas.height = sq;
  canvas.style.top = ((iH - canvas.height) / 2) + 'px';
  canvas.style.left = ((iW - canvas.width) / 2) + 'px';

  let max = gl.getParameter(gl.MAX_TEXTURE_SIZE);
  if (max < image.height) {
    tex.width = Math.floor(image.width * (max / image.height));
    tex.height = max;
  }else{
    tex.width = image.width;
    tex.height = image.height;
  }

  rect = canvas.getBoundingClientRect();

    gl.viewport(0, 0, canvas.width, canvas.height);
};

function start() {
  image = document.createElement('img');
  canvas = document.getElementById('c');
  gl = canvas.getContext('experimental-webgl');
  tex = document.createElement('canvas');
  ctx = tex.getContext('2d');

  var buttons = document.getElementsByTagName('button');
  for (let i = 0; i < buttons.length; i++) {
      let url = buttons[i].getAttribute('data-url');
      buttons[i].addEventListener('click', function() {
        image.src = url;
        m[0] = 0;
        touched = false;
        clearTimeout(timeout);
        demo();
      });
  }

  image.onload = function() {
    resize();
    ctx.drawImage(image, 0, 0, tex.width, tex.height);
    main();
  };

  image.crossOrigin = 'anonymous';
  image.src = 'https://s3misc20190920.s3.amazonaws.com/3434441128_1430362800_o.png';
}

function main() {
  var vshader = load_shader('vertex-shader', gl.VERTEX_SHADER);
  var fshader = load_shader('fragment-shader', gl.FRAGMENT_SHADER);

  gl.program = gl.createProgram();
  gl.attachShader(gl.program, vshader);
  gl.attachShader(gl.program, fshader);
  gl.linkProgram(gl.program);
  gl.useProgram(gl.program);

  var acoords = gl.getAttribLocation(gl.program, 'a_coords');

  uscale = gl.getUniformLocation(gl.program, 'u_scale');
  uzoom = gl.getUniformLocation(gl.program, 'u_zoom');

  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  with(gl) {
    texParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE);
    texParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE);
    texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST);
    texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST);
  }

  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, tex);
  gl.uniform1f(uscale, tex.width / tex.height / 10);

  var buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.enableVertexAttribArray(acoords);
  gl.vertexAttribPointer(acoords, 2, gl.FLOAT, false, 0, 0);

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    -1.0,  -1.0,
    1.0,  -1.0,
    -1.0,  1.0,
    -1.0,  1.0,
    1.0,  -1.0,
    1.0,  1.0
  ]), gl.STATIC_DRAW);

  if (init) {
    init = false;
    loop();
  }

  touched = false;
  clearTimeout(timeout);
  demo();

}

function load_shader(el, type) {
  var script = document.getElementById(el);
  var shader = gl.createShader(type);

  gl.shaderSource(shader, script.text);
  gl.compileShader(shader);

  return shader;
}

function loop() {
  slowX *= 0.95;
  slowX += 0.05 * m[0] / iW;

  zoom = Math.pow(10, -36 * Math.max(0.003, Math.min(1, slowX)));

  gl.uniform1f(uzoom, zoom);
  gl.drawArrays(gl.TRIANGLES, 0, 6);

  requestAnimationFrame(loop);
}

function demo() {
  if (touched) return;
  if (m[0] < iW * 0.99) {
    m[0]+= iW / 100 >> 0;
    timeout = setTimeout(demo, 100);
  }
}

function mouse(e) {
  e.preventDefault();
  if (typeof(e.touches) != 'undefined') e = e.touches[0];
  touched = true;
  m[0] = e.pageX;
  m[1] = e.pageY;
}

Комментарии

  • Facebook
  • Вконтакте

Похожие статьи