Глубокий фрактальный зум
Три разновидности фрактала. При перемещении курсора мы либо погружаемся, либо всплываем.
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;
}