|
<meta charset="utf-8"> |
|
<style> |
|
|
|
#under-construction { |
|
display: none; |
|
position: absolute; |
|
top: 200px; |
|
left: 300px; |
|
font-size: 40px; |
|
} |
|
|
|
#flexer { |
|
display: flex; |
|
justify-content: space-around; |
|
align-items: center; |
|
flex-wrap: wrap; |
|
} |
|
|
|
.relativer { |
|
position: relative; |
|
} |
|
|
|
.cropped-blurred-points, #transparency, #pattern { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
} |
|
|
|
.cropped-blurred-points { |
|
transform-origin: 50% 50%; |
|
#animation: rotating 10s linear infinite; |
|
} |
|
|
|
.title { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
color: #aaa; |
|
} |
|
|
|
.title div { |
|
transform: translate(-50%, -50%); |
|
text-align: center; |
|
} |
|
|
|
#explanation .title div { |
|
line-height: 0.7em; |
|
transform: translate(-50%, calc(-50% - 12px)); |
|
} |
|
|
|
@keyframes rotating { |
|
from{ |
|
transform: rotate(0deg); |
|
} |
|
to{ |
|
transform: rotate(360deg); |
|
} |
|
} |
|
|
|
.hidden { |
|
display: none; |
|
} |
|
</style> |
|
<body> |
|
<div id="flexer"> |
|
<div id="explanation" class="relativer"> |
|
<canvas class="background"></canvas> |
|
<canvas id="transparency"></canvas> |
|
<canvas id="pattern"></canvas> |
|
<div class="title"><div>4 rings<br/>+<br/>2 patterns</div></div> |
|
</div> |
|
<div id="spiral" class="relativer"> |
|
<canvas class="background"></canvas> |
|
<canvas class="cropped-blurred-points"></canvas> |
|
<div class="title"><div>spiral</div></div> |
|
</div> |
|
<div id="zigzag" class="relativer"> |
|
<canvas class="background"></canvas> |
|
<canvas class="cropped-blurred-points"></canvas> |
|
<div class="title"><div>zigzag</div></div> |
|
</div> |
|
<div id="cardioid" class="relativer"> |
|
<canvas class="background"></canvas> |
|
<canvas class="cropped-blurred-points"></canvas> |
|
<div class="title"><div>cardioid</div></div> |
|
</div> |
|
<div id="random" class="relativer"> |
|
<canvas class="background"></canvas> |
|
<canvas class="cropped-blurred-points"></canvas> |
|
<div class="title"><div>random</div></div> |
|
</div> |
|
<div id="updated-random" class="relativer"> |
|
<canvas class="background"></canvas> |
|
<canvas class="cropped-blurred-points"></canvas> |
|
<div class="title"><div>updated<br/>random</div></div> |
|
</div> |
|
</div> |
|
<canvas id="points" class="hidden"></canvas> |
|
<canvas id="blurred-points" class="hidden"></canvas> |
|
<div id="under-construction"> |
|
UNDER CONSTRUCTION |
|
</div> |
|
<script src="https://d3js.org/d3-timer.v1.min.js"></script> |
|
<script> |
|
const _PI = Math.PI, |
|
_2PI = 2*Math.PI, |
|
cos = Math.cos, |
|
sin = Math.sin, |
|
random = Math.random; |
|
|
|
//begin: display conf. |
|
const size = 250, |
|
halfSize = size/2, |
|
pointsPerRing = 72, // multipple of 4 |
|
spaceBetweenRings = halfSize/7, |
|
spaceFromOuter = 1.5*spaceBetweenRings; |
|
let ringNumber = 4; |
|
//end: display conf. |
|
|
|
const illusionTypes = ["spiral", "zigzag", "cardioid", "random", "updated-random"]; |
|
|
|
//begin: reusable DOM elements |
|
const transparencyCanvas = document.querySelector("#transparency"), |
|
pointsCanvas = document.querySelector("#points"), |
|
blurredPointsCanvas = document.querySelector("#blurred-points"); |
|
//end: reusable DOM elements |
|
|
|
initLayout(); |
|
drawStaticCanvases(); |
|
|
|
d3.interval(function(elapsed) { |
|
makeIllusion("updated-random"); |
|
}, 1000); |
|
|
|
function initLayout() { |
|
document.querySelectorAll("canvas").forEach(function(c){ |
|
c.width = size; |
|
c.height = size; |
|
}); |
|
} |
|
|
|
function drawStaticCanvases() { |
|
makeBackgrounds(); |
|
makeTransparency(); |
|
makePattern(); |
|
|
|
illusionTypes.forEach(it=>makeIllusion(it)); |
|
} |
|
|
|
function makeBackgrounds() { |
|
let backgroundContext; |
|
|
|
document.querySelectorAll(".background").forEach(function(c){ |
|
backgroundContext = c.getContext("2d"); |
|
backgroundContext.clearRect(0,0,size,size); |
|
|
|
backgroundContext.fillStyle = "grey"; |
|
backgroundContext.filter = "blur(2px)"; |
|
backgroundContext.translate(halfSize, halfSize); |
|
backgroundContext.beginPath(); |
|
backgroundContext.arc(0, 0, halfSize-5, 0, _2PI); |
|
backgroundContext.fill(); |
|
|
|
backgroundContext.resetTransform(); |
|
}) |
|
} |
|
|
|
function makePattern() { |
|
const patternCanvas = document.querySelector("#pattern") |
|
patternContext = patternCanvas.getContext("2d"), |
|
blur = 1, |
|
pointRadius = 2; |
|
|
|
patternContext.clearRect(0,0,size,size); |
|
patternContext.filter = "blur("+blur+"px)"; |
|
for (let p=0; p<2; p++) { |
|
patternContext.translate(halfSize+(-13+17*p)*pointRadius, halfSize+20); |
|
drawPoint(patternContext, 0, 0, pointRadius, "black"); |
|
patternContext.translate(3*pointRadius, 0); |
|
if (p<1) { |
|
drawPoint(patternContext, pointRadius, _PI/2, pointRadius, "black"); |
|
drawPoint(patternContext, pointRadius, -_PI/2, pointRadius, "white"); |
|
} else { |
|
drawPoint(patternContext, pointRadius, _PI/2, pointRadius, "white"); |
|
drawPoint(patternContext, pointRadius, -_PI/2, pointRadius, "black"); |
|
} |
|
patternContext.translate(3*pointRadius, 0); |
|
drawPoint(patternContext, 0, 0, pointRadius, "white"); |
|
patternContext.translate(3*pointRadius, 0); |
|
if (p<1) { |
|
drawPoint(patternContext, pointRadius, _PI/2, pointRadius, "white"); |
|
drawPoint(patternContext, pointRadius, -_PI/2, pointRadius, "black"); |
|
} else { |
|
drawPoint(patternContext, pointRadius, _PI/2, pointRadius, "black"); |
|
drawPoint(patternContext, pointRadius, -_PI/2, pointRadius, "white"); |
|
} |
|
patternContext.resetTransform(); |
|
} |
|
} |
|
|
|
function makeTransparency() { |
|
const transparencyContext = transparencyCanvas.getContext("2d"), |
|
lineWidth = halfSize/20/2, |
|
blur = lineWidth/2; |
|
let ringRadius; |
|
|
|
transparencyContext.clearRect(0,0,size,size); |
|
|
|
//begin: draw greyscale 'mate' image |
|
transparencyContext.fillRect(0,0,size,size); |
|
|
|
transparencyContext.lineWidth = lineWidth; |
|
transparencyContext.strokeStyle = "white"; |
|
transparencyContext.filter = "blur("+blur+"px)"; |
|
transparencyContext.translate(halfSize, halfSize); // position at canvas' center |
|
transparencyContext.rotate(-_PI/2); |
|
|
|
for (let r=0; r<ringNumber; r++) { |
|
ringRadius = halfSize-spaceFromOuter-r*spaceBetweenRings; |
|
transparencyContext.beginPath(); |
|
transparencyContext.arc(0, 0, ringRadius, 0, _2PI); |
|
//transparencyContext.arc(0, 0, ringRadius, 0, _2PI*(0.5+0.5*random())); |
|
transparencyContext.stroke(); |
|
transparencyContext.stroke(); //twice for saturation |
|
} |
|
//end: draw greyscale 'mate' image |
|
|
|
//begin: update alpha channel |
|
const imageData = transparencyContext.getImageData(0, 0, size, size), |
|
pixels = imageData.data; |
|
let pixel = 0; |
|
|
|
for(pixel=0 ; pixel<size*size*4; pixel+=4) { |
|
pixels[pixel+3]=pixels[pixel]; |
|
} |
|
transparencyContext.putImageData(imageData, 0, 0); |
|
//end: update alpha channel |
|
|
|
transparencyContext.resetTransform(); |
|
} |
|
|
|
function makeIllusion(illusionType){ |
|
switch (illusionType) { |
|
case "spiral": |
|
makeSpiralPoints(); |
|
break; |
|
case "zigzag": |
|
makeZigZagPoints(); |
|
break; |
|
case "cardioid": |
|
makeCardioidPoints(); |
|
break; |
|
default: |
|
makeRandomPoints(); |
|
break; |
|
} |
|
makeBlurredPoints(); |
|
makeCroppedBlurredPointsCanvas( |
|
document.querySelector("#"+illusionType+" .cropped-blurred-points") |
|
); |
|
} |
|
|
|
function makeSpiralPoints() { |
|
const pointsContext = pointsCanvas.getContext("2d"); |
|
let ringRadius, pointRadius, ringRadiusPlus, ringRadiusMinus; |
|
|
|
pointsContext.clearRect(0,0,size,size); |
|
pointsContext.translate(halfSize, halfSize); // position at canvas' center |
|
pointsContext.rotate(-_PI/2); |
|
|
|
for (let r=0; r<ringNumber; r++) { |
|
ringRadius = halfSize-spaceFromOuter-r*spaceBetweenRings; |
|
pointRadius = ringRadius/pointsPerRing*2; |
|
ringRadiusPlus = ringRadius+1.5*pointRadius; |
|
ringRadiusMinus = ringRadius-1.5*pointRadius; |
|
for (let p=0, j=0; p<pointsPerRing; p++, j+=_2PI/pointsPerRing) { |
|
if (p%4 === 0) { |
|
drawPoint(pointsContext, ringRadius, j, pointRadius, "black"); |
|
} |
|
else if (p%4 === 1) { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "white"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "black"); |
|
} |
|
else if (p%4 === 2) { |
|
drawPoint(pointsContext, ringRadius, j, pointRadius, "white"); |
|
} |
|
else /*(p%4 === 3)*/ { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "black"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "white"); |
|
} |
|
} |
|
} |
|
|
|
pointsContext.resetTransform(); |
|
} |
|
|
|
function makeZigZagPoints() { |
|
const pointsContext = pointsCanvas.getContext("2d"); |
|
let ringRadius, pointRadius, ringRadiusPlus, ringRadiusMinus; |
|
|
|
pointsContext.clearRect(0,0,size,size); |
|
pointsContext.translate(halfSize, halfSize); // position at canvas' center |
|
pointsContext.rotate(-_PI/2); |
|
|
|
for (let r=0; r<ringNumber; r++) { |
|
ringRadius = halfSize-spaceFromOuter-r*spaceBetweenRings; |
|
pointRadius = ringRadius/pointsPerRing*2; |
|
ringRadiusPlus = ringRadius+1.5*pointRadius; |
|
ringRadiusMinus = ringRadius-1.5*pointRadius; |
|
for (let p=0, j=0; p<pointsPerRing; p++, j+=_2PI/pointsPerRing) { |
|
if (p%4 === 0) { |
|
drawPoint(pointsContext, ringRadius, j, pointRadius, "black"); |
|
} |
|
else if (p%4 === 1) { |
|
if (r%2) { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "white"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "black"); |
|
} else { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "black"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "white"); |
|
} |
|
} |
|
else if (p%4 === 2) { |
|
drawPoint(pointsContext, ringRadius, j, pointRadius, "white"); |
|
} |
|
else /*(p%4 === 3)*/ { |
|
if (r%2) { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "black"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "white"); |
|
} else { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "white"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "black"); |
|
} |
|
} |
|
} |
|
} |
|
|
|
pointsContext.resetTransform(); |
|
} |
|
|
|
function makeCardioidPoints() { |
|
const pointsContext = pointsCanvas.getContext("2d"); |
|
let ringRadius, pointRadius, ringRadiusPlus, ringRadiusMinus; |
|
|
|
pointsContext.clearRect(0,0,size,size); |
|
pointsContext.translate(halfSize, halfSize); // position at canvas' center |
|
pointsContext.rotate(-_PI/2); |
|
|
|
for (let r=0; r<ringNumber; r++) { |
|
ringRadius = halfSize-spaceFromOuter-r*spaceBetweenRings; |
|
pointRadius = ringRadius/pointsPerRing*2; |
|
ringRadiusPlus = ringRadius+1.5*pointRadius; |
|
ringRadiusMinus = ringRadius-1.5*pointRadius; |
|
for (let p=0, j=0; p<pointsPerRing; p++, j+=_2PI/pointsPerRing) { |
|
if (p%4 === 0) { |
|
drawPoint(pointsContext, ringRadius, j, pointRadius, "black"); |
|
} |
|
else if (p%4 === 1) { |
|
if (p<pointsPerRing/2) { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "white"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "black"); |
|
} else { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "black"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "white"); |
|
} |
|
} |
|
else if (p%4 === 2) { |
|
drawPoint(pointsContext, ringRadius, j, pointRadius, "white"); |
|
} |
|
else /*(p%4 === 3)*/ { |
|
if (p<pointsPerRing/2) { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "black"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "white"); |
|
} else { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "white"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "black"); |
|
} |
|
} |
|
} |
|
} |
|
|
|
pointsContext.resetTransform(); |
|
} |
|
|
|
function makeRandomPoints() { |
|
const pointsContext = pointsCanvas.getContext("2d"); |
|
let ringRadius, pointRadius, ringRadiusPlus, ringRadiusMinus, slope; |
|
|
|
pointsContext.clearRect(0,0,size,size); |
|
pointsContext.translate(halfSize, halfSize); // position at canvas' center |
|
pointsContext.rotate(-_PI/2); |
|
|
|
for (let r=0; r<ringNumber; r++) { |
|
ringRadius = halfSize-spaceFromOuter-r*spaceBetweenRings; |
|
pointRadius = ringRadius/pointsPerRing*2; |
|
ringRadiusPlus = ringRadius+1.5*pointRadius; |
|
ringRadiusMinus = ringRadius-1.5*pointRadius; |
|
for (let p=0, j=0; p<pointsPerRing; p++, j+=_2PI/pointsPerRing) { |
|
if (p%16 === 0) { |
|
//lower modulos make more heratic illusion |
|
slope = (random()>0.5); |
|
} |
|
|
|
if (p%4 === 0) { |
|
drawPoint(pointsContext, ringRadius, j, pointRadius, "black"); |
|
} |
|
else if (p%4 === 1) { |
|
if (slope) { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "white"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "black"); |
|
} else { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "black"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "white"); |
|
} |
|
} |
|
else if (p%4 === 2) { |
|
drawPoint(pointsContext, ringRadius, j, pointRadius, "white"); |
|
} |
|
else { |
|
if (slope) { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "black"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "white"); |
|
} else { |
|
drawPoint(pointsContext, ringRadiusPlus, j, pointRadius, "white"); |
|
drawPoint(pointsContext, ringRadiusMinus, j, pointRadius, "black"); |
|
} |
|
} |
|
} |
|
} |
|
|
|
pointsContext.resetTransform(); |
|
} |
|
|
|
function drawPoint(context, distance, angle, radius, color) { |
|
context.beginPath(); |
|
context.fillStyle = color; |
|
context.arc(distance*cos(angle), distance*sin(angle), radius, 0, _2PI); |
|
context.fill(); |
|
} |
|
|
|
function makeBlurredPoints() { |
|
const blurredPointsContext = blurredPointsCanvas.getContext("2d"), |
|
imageData = blurredPointsContext.createImageData(size, size), |
|
lineWidth = halfSize/20, |
|
blur = lineWidth/5; |
|
|
|
blurredPointsContext.clearRect(0,0,size,size); |
|
|
|
blurredPointsContext.filter = "blur("+blur+"px)"; |
|
blurredPointsContext.drawImage(pointsCanvas, 0, 0); |
|
blurredPointsContext.drawImage(pointsCanvas, 0, 0); //twice for saturation |
|
} |
|
|
|
function makeCroppedBlurredPointsCanvas(canvas) { |
|
const context = canvas.getContext("2d"); |
|
|
|
context.clearRect(0,0,size,size); |
|
|
|
//begin: use 'blurred-points' to color pixels of the 'transparency' canvas |
|
context.clearRect(0, 0, size, size); |
|
context.globalCompositeOperation = "source-over"; |
|
context.drawImage(blurredPointsCanvas, 0, 0); |
|
context.globalCompositeOperation = "destination-in"; |
|
context.drawImage(transparencyCanvas, 0, 0); |
|
//begin: use 'blurred-points' to color pixels of the 'transparency' canvas |
|
} |
|
</script> |