-
-
Save WyattJia/40d0f7635f30ad77103aaf96552fe2bd to your computer and use it in GitHub Desktop.
Shape Shifter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<!-- saved from url=(0028)https://hacpai.com/halt.html --> | |
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<title>发现一个问题</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<link rel="icon" type="image/png" href="https://hacpai.com/$%7BstaticServePath%7D/images/favicon.png"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> | |
<style> | |
html { | |
height: 100%; | |
} | |
body { | |
font-family: "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", "Microsoft Yahei", sans-serif; | |
background: #79a8ae; | |
color: #CFEBE4; | |
font-size: 18px; | |
line-height: 2; | |
letter-spacing: 1.2px; | |
margin: 0; | |
} | |
a { | |
color: #ebf7f4; | |
} | |
.body--ready { | |
background: -webkit-linear-gradient(top, rgb(203, 235, 219) 0%, rgb(55, 148, 192) 120%); | |
background: -moz-linear-gradient(top, rgb(203, 235, 219) 0%, rgb(55, 148, 192) 120%); | |
background: -o-linear-gradient(top, rgb(203, 235, 219) 0%, rgb(55, 148, 192) 120%); | |
background: -ms-linear-gradient(top, rgb(203, 235, 219) 0%, rgb(55, 148, 192) 120%); | |
background: linear-gradient(top, rgb(203, 235, 219) 0%, rgb(55, 148, 192) 120%); | |
} | |
.text { | |
position: fixed; | |
bottom: 100px; | |
text-align: center; | |
width: 100%; | |
} | |
.canvas { | |
margin: 0 auto; | |
display: block; | |
} | |
</style> | |
<meta name="chromesniffer" id="chromesniffer_meta" content="{"Java":-1}"><script type="text/javascript" src="chrome-extension://fhhdlnnepfjhlhilgmeepgkhjmhhhjkh/js/detector.js"></script></head> | |
<body class="body--ready" data-pinterest-extension-installed="cr1.39.1"> | |
<canvas class="canvas" width="1820" height="905"></canvas> | |
<p class="text"> | |
<a href="https://viosey.com" target="_blank">viosey.com</a> | |
</p> | |
<script> | |
var S = { | |
init: function () { | |
S.Drawing.init('.canvas'); | |
document.body.classList.add('body--ready'); | |
S.UI.simulate("I'm|Viosey|Nice|to|Meet YOU!|#countdown 3"); | |
S.Drawing.loop(function () { | |
S.Shape.render(); | |
}); | |
} | |
}; | |
S.Drawing = (function () { | |
var canvas, | |
context, | |
renderFn, | |
requestFrame = window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.oRequestAnimationFrame || | |
window.msRequestAnimationFrame || | |
function (callback) { | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
return { | |
init: function (el) { | |
canvas = document.querySelector(el); | |
context = canvas.getContext('2d'); | |
this.adjustCanvas(); | |
window.addEventListener('resize', function (e) { | |
S.Drawing.adjustCanvas(); | |
}); | |
}, | |
loop: function (fn) { | |
renderFn = !renderFn ? fn : renderFn; | |
this.clearFrame(); | |
renderFn(); | |
requestFrame.call(window, this.loop.bind(this)); | |
}, | |
adjustCanvas: function () { | |
canvas.width = window.innerWidth - 100; | |
canvas.height = window.innerHeight - 30; | |
}, | |
clearFrame: function () { | |
context.clearRect(0, 0, canvas.width, canvas.height); | |
}, | |
getArea: function () { | |
return {w: canvas.width, h: canvas.height}; | |
}, | |
drawCircle: function (p, c) { | |
context.fillStyle = c.render(); | |
context.beginPath(); | |
context.arc(p.x, p.y, p.z, 0, 2 * Math.PI, true); | |
context.closePath(); | |
context.fill(); | |
} | |
}; | |
}()); | |
S.UI = (function () { | |
var interval, | |
currentAction, | |
time, | |
maxShapeSize = 30, | |
sequence = [], | |
cmd = '#'; | |
function formatTime(date) { | |
var h = date.getHours(), | |
m = date.getMinutes(), | |
m = m < 10 ? '0' + m : m; | |
return h + ':' + m; | |
} | |
function getValue(value) { | |
return value && value.split(' ')[1]; | |
} | |
function getAction(value) { | |
value = value && value.split(' ')[0]; | |
return value && value[0] === cmd && value.substring(1); | |
} | |
function timedAction(fn, delay, max, reverse) { | |
clearInterval(interval); | |
currentAction = reverse ? max : 1; | |
fn(currentAction); | |
if (!max || (!reverse && currentAction < max) || (reverse && currentAction > 0)) { | |
interval = setInterval(function () { | |
currentAction = reverse ? currentAction - 1 : currentAction + 1; | |
fn(currentAction); | |
if ((!reverse && max && currentAction === max) || (reverse && currentAction === 0)) { | |
clearInterval(interval); | |
} | |
}, delay); | |
} | |
} | |
function performAction(value) { | |
var action, | |
value, | |
current; | |
sequence = typeof (value) === 'object' ? value : sequence.concat(value.split('|')); | |
timedAction(function (index) { | |
current = sequence.shift(); | |
action = getAction(current); | |
value = getValue(current); | |
switch (action) { | |
case 'countdown': | |
value = parseInt(value) || 10; | |
value = value > 0 ? value : 10; | |
timedAction(function (index) { | |
if (index === 0) { | |
if (sequence.length === 0) { | |
S.Shape.switchShape(S.ShapeBuilder.letter('')); | |
} else { | |
performAction(sequence); | |
} | |
} else { | |
S.Shape.switchShape(S.ShapeBuilder.letter(index), true); | |
} | |
}, 1000, value, true); | |
break; | |
case 'rectangle': | |
value = value && value.split('x'); | |
value = (value && value.length === 2) ? value : [maxShapeSize, maxShapeSize / 2]; | |
S.Shape.switchShape(S.ShapeBuilder.rectangle(Math.min(maxShapeSize, parseInt(value[0])), Math.min(maxShapeSize, parseInt(value[1])))); | |
break; | |
case 'circle': | |
value = parseInt(value) || maxShapeSize; | |
value = Math.min(value, maxShapeSize); | |
S.Shape.switchShape(S.ShapeBuilder.circle(value)); | |
break; | |
case 'time': | |
var t = formatTime(new Date()); | |
if (sequence.length > 0) { | |
S.Shape.switchShape(S.ShapeBuilder.letter(t)); | |
} else { | |
timedAction(function () { | |
t = formatTime(new Date()); | |
if (t !== time) { | |
time = t; | |
S.Shape.switchShape(S.ShapeBuilder.letter(time)); | |
} | |
}, 1000); | |
} | |
break; | |
default: | |
S.Shape.switchShape(S.ShapeBuilder.letter(current[0] === cmd ? 'HacPai' : current)); | |
} | |
}, 2000, sequence.length); | |
} | |
return { | |
simulate: function (action) { | |
performAction(action); | |
} | |
}; | |
}()); | |
S.Point = function (args) { | |
this.x = args.x; | |
this.y = args.y; | |
this.z = args.z; | |
this.a = args.a; | |
this.h = args.h; | |
}; | |
S.Color = function (r, g, b, a) { | |
this.r = r; | |
this.g = g; | |
this.b = b; | |
this.a = a; | |
}; | |
S.Color.prototype = { | |
render: function () { | |
return 'rgba(' + this.r + ',' + +this.g + ',' + this.b + ',' + this.a + ')'; | |
} | |
}; | |
S.Dot = function (x, y) { | |
this.p = new S.Point({ | |
x: x, | |
y: y, | |
z: 5, | |
a: 1, | |
h: 0 | |
}); | |
this.e = 0.07; | |
this.s = true; | |
this.c = new S.Color(255, 255, 255, this.p.a); | |
this.t = this.clone(); | |
this.q = []; | |
}; | |
S.Dot.prototype = { | |
clone: function () { | |
return new S.Point({ | |
x: this.x, | |
y: this.y, | |
z: this.z, | |
a: this.a, | |
h: this.h | |
}); | |
}, | |
_draw: function () { | |
this.c.a = this.p.a; | |
S.Drawing.drawCircle(this.p, this.c); | |
}, | |
_moveTowards: function (n) { | |
var details = this.distanceTo(n, true), | |
dx = details[0], | |
dy = details[1], | |
d = details[2], | |
e = this.e * d; | |
if (this.p.h === -1) { | |
this.p.x = n.x; | |
this.p.y = n.y; | |
return true; | |
} | |
if (d > 1) { | |
this.p.x -= ((dx / d) * e); | |
this.p.y -= ((dy / d) * e); | |
} else { | |
if (this.p.h > 0) { | |
this.p.h--; | |
} else { | |
return true; | |
} | |
} | |
return false; | |
}, | |
_update: function () { | |
if (this._moveTowards(this.t)) { | |
var p = this.q.shift(); | |
if (p) { | |
this.t.x = p.x || this.p.x; | |
this.t.y = p.y || this.p.y; | |
this.t.z = p.z || this.p.z; | |
this.t.a = p.a || this.p.a; | |
this.p.h = p.h || 0; | |
} else { | |
if (this.s) { | |
this.p.x -= Math.sin(Math.random() * 3.142); | |
this.p.y -= Math.sin(Math.random() * 3.142); | |
} else { | |
this.move(new S.Point({ | |
x: this.p.x + (Math.random() * 50) - 25, | |
y: this.p.y + (Math.random() * 50) - 25 | |
})); | |
} | |
} | |
} | |
d = this.p.a - this.t.a; | |
this.p.a = Math.max(0.1, this.p.a - (d * 0.05)); | |
d = this.p.z - this.t.z; | |
this.p.z = Math.max(1, this.p.z - (d * 0.05)); | |
}, | |
distanceTo: function (n, details) { | |
var dx = this.p.x - n.x, | |
dy = this.p.y - n.y, | |
d = Math.sqrt(dx * dx + dy * dy); | |
return details ? [dx, dy, d] : d; | |
}, | |
move: function (p, avoidStatic) { | |
if (!avoidStatic || (avoidStatic && this.distanceTo(p) > 1)) { | |
this.q.push(p); | |
} | |
}, | |
render: function () { | |
this._update(); | |
this._draw(); | |
} | |
}; | |
S.ShapeBuilder = (function () { | |
var gap = 13, | |
shapeCanvas = document.createElement('canvas'), | |
shapeContext = shapeCanvas.getContext('2d'), | |
fontSize = 500, | |
fontFamily = 'Avenir, Helvetica Neue, Helvetica, Arial, sans-serif'; | |
function fit() { | |
shapeCanvas.width = Math.floor(window.innerWidth / gap) * gap; | |
shapeCanvas.height = Math.floor(window.innerHeight / gap) * gap; | |
shapeContext.fillStyle = 'red'; | |
shapeContext.textBaseline = 'middle'; | |
shapeContext.textAlign = 'center'; | |
} | |
function processCanvas() { | |
var pixels = shapeContext.getImageData(0, 0, shapeCanvas.width, shapeCanvas.height).data; | |
dots = [], | |
pixels, | |
x = 0, | |
y = 0, | |
fx = shapeCanvas.width, | |
fy = shapeCanvas.height, | |
w = 0, | |
h = 0; | |
for (var p = 0; p < pixels.length; p += (4 * gap)) { | |
if (pixels[p + 3] > 0) { | |
dots.push(new S.Point({ | |
x: x, | |
y: y | |
})); | |
w = x > w ? x : w; | |
h = y > h ? y : h; | |
fx = x < fx ? x : fx; | |
fy = y < fy ? y : fy; | |
} | |
x += gap; | |
if (x >= shapeCanvas.width) { | |
x = 0; | |
y += gap; | |
p += gap * 4 * shapeCanvas.width; | |
} | |
} | |
return {dots: dots, w: w + fx, h: h + fy}; | |
} | |
function setFontSize(s) { | |
shapeContext.font = 'bold ' + s + 'px ' + fontFamily; | |
} | |
function isNumber(n) { | |
return !isNaN(parseFloat(n)) && isFinite(n); | |
} | |
function init() { | |
fit(); | |
window.addEventListener('resize', fit); | |
} | |
// Init | |
init(); | |
return { | |
imageFile: function (url, callback) { | |
var image = new Image(), | |
a = S.Drawing.getArea(); | |
image.onload = function () { | |
shapeContext.clearRect(0, 0, shapeCanvas.width, shapeCanvas.height); | |
shapeContext.drawImage(this, 0, 0, a.h * 0.6, a.h * 0.6); | |
callback(processCanvas()); | |
}; | |
image.onerror = function () { | |
callback(S.ShapeBuilder.letter('What?')); | |
}; | |
image.src = url; | |
}, | |
circle: function (d) { | |
var r = Math.max(0, d) / 2; | |
shapeContext.clearRect(0, 0, shapeCanvas.width, shapeCanvas.height); | |
shapeContext.beginPath(); | |
shapeContext.arc(r * gap, r * gap, r * gap, 0, 2 * Math.PI, false); | |
shapeContext.fill(); | |
shapeContext.closePath(); | |
return processCanvas(); | |
}, | |
letter: function (l) { | |
var s = 0; | |
setFontSize(fontSize); | |
s = Math.min(fontSize, | |
(shapeCanvas.width / shapeContext.measureText(l).width) * 0.8 * fontSize, | |
(shapeCanvas.height / fontSize) * (isNumber(l) ? 1 : 0.45) * fontSize); | |
setFontSize(s); | |
shapeContext.clearRect(0, 0, shapeCanvas.width, shapeCanvas.height); | |
shapeContext.fillText(l, shapeCanvas.width / 2, shapeCanvas.height / 2); | |
return processCanvas(); | |
}, | |
rectangle: function (w, h) { | |
var dots = [], | |
width = gap * w, | |
height = gap * h; | |
for (var y = 0; y < height; y += gap) { | |
for (var x = 0; x < width; x += gap) { | |
dots.push(new S.Point({ | |
x: x, | |
y: y | |
})); | |
} | |
} | |
return {dots: dots, w: width, h: height}; | |
} | |
}; | |
}()); | |
S.Shape = (function () { | |
var dots = [], | |
width = 0, | |
height = 0, | |
cx = 0, | |
cy = 0; | |
function compensate() { | |
var a = S.Drawing.getArea(); | |
cx = a.w / 2 - width / 2; | |
cy = a.h / 2 - height / 2; | |
} | |
return { | |
shuffleIdle: function () { | |
var a = S.Drawing.getArea(); | |
for (var d = 0; d < dots.length; d++) { | |
if (!dots[d].s) { | |
dots[d].move({ | |
x: Math.random() * a.w, | |
y: Math.random() * a.h | |
}); | |
} | |
} | |
}, | |
switchShape: function (n, fast) { | |
var size, | |
a = S.Drawing.getArea(); | |
width = n.w; | |
height = n.h; | |
compensate(); | |
if (n.dots.length > dots.length) { | |
size = n.dots.length - dots.length; | |
for (var d = 1; d <= size; d++) { | |
dots.push(new S.Dot(a.w / 2, a.h / 2)); | |
} | |
} | |
var d = 0, | |
i = 0; | |
while (n.dots.length > 0) { | |
i = Math.floor(Math.random() * n.dots.length); | |
dots[d].e = fast ? 0.25 : (dots[d].s ? 0.14 : 0.11); | |
if (dots[d].s) { | |
dots[d].move(new S.Point({ | |
z: Math.random() * 20 + 10, | |
a: Math.random(), | |
h: 18 | |
})); | |
} else { | |
dots[d].move(new S.Point({ | |
z: Math.random() * 5 + 5, | |
h: fast ? 18 : 30 | |
})); | |
} | |
dots[d].s = true; | |
dots[d].move(new S.Point({ | |
x: n.dots[i].x + cx, | |
y: n.dots[i].y + cy, | |
a: 1, | |
z: 5, | |
h: 0 | |
})); | |
n.dots = n.dots.slice(0, i).concat(n.dots.slice(i + 1)); | |
d++; | |
} | |
for (var i = d; i < dots.length; i++) { | |
if (dots[i].s) { | |
dots[i].move(new S.Point({ | |
z: Math.random() * 20 + 10, | |
a: Math.random(), | |
h: 20 | |
})); | |
dots[i].s = false; | |
dots[i].e = 0.04; | |
dots[i].move(new S.Point({ | |
x: Math.random() * a.w, | |
y: Math.random() * a.h, | |
a: 0.3, //.4 | |
z: Math.random() * 4, | |
h: 0 | |
})); | |
} | |
} | |
}, | |
render: function () { | |
for (var d = 0; d < dots.length; d++) { | |
dots[d].render(); | |
} | |
} | |
}; | |
}()); | |
S.init(); | |
</script> | |
</body><div></div></html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment