Mann-der-rennt-einstellbar.php


Quell Code


<style>

  a, a:link, a:visited {

  color: #ddd;

}

a:hover {

  color: #fff;

}



body {

  background: #111;

  font-family: sans-serif;

}

.button {

  background: grey;

  display: inline-block;

  padding: 10px;

  margin: 2px;

  cursor: pointer;

}

.button.selected {

  background: #333;

  color: white;

}

.instructions {

  position: absolute; 

  top: 530px;

  width: 400px;

  color: #aaa;

}



  

</style>







<div class='instructions'>

  <p>Click the 'editor' button to make your own human movement.</p>

  <p>If you're happy with your creation, copy and paste the output and fork it. Comment with your link.</p>

  <p>You can ignore 'body', the body is a fixed point, think of it as his hips.</p>

  <p>Each limb has the following properties:<br>

    range is how much the limb moves,<br>

    baserot is the midpoint/starting rotation,<br>

    length is the... length,<br>

    offset is the sync of the animation cycle (think of <a href='https://www.google.com.au/search?q=2+sinewaves&tbm=isch'>two sine waves</a> slightly out of sync<br>

    <br>

    (all rotations are from 0 to 2 * Pi)</p>

 

  <p>Note: the editor button will jump in to 'JS + Canvas' mode to ensure your creation updates properly. Live editing 'JS + Divs' and 'CSS Keyframes + Divs' has not been fully implemented.</p>

</div>

<script>

  /*



try dragging the sliders. works best in js + canvas mode.



can't talk now, need to run.



long story short, i made this initially 10 years ago in flash, started remaking it one month ago. desandro published something similar last week. bzzt.



was going to write a blog post, explain the different rendering techniques:



1) css keyframes

2) nested divs with transforms and js

3) canvas and js



the keyframes method is quite hard, indeed slightly out on some limbs, but this is novelty.



you can throw all sorts of bodies at this.



eg. try uncommenting //limbs = iceskating; below... also i had a spider as a work in progress... 



this was originally written to run in node (exporting frames to pngs) for the creation of things like:

https://twitter.com/raurir/status/613300949407272960



*/



var con = console;





var dom = (function() {



    function setProps(el, props) {

		for (var p in props) {

			if (p == "style") {

				for (var s in props[p]) {

					el[p][s] = props[p][s];

				}

			} else {

				el[p] = props[p];

			}

		}

		return el;

	}



	function setAttributes(el, attrs) {

		for (var p in attrs) {

			el.setAttribute(p,attrs[p]);

		}

		return el;

	}



	function element(element, props) {

		var el = document.createElement(element);

		setProps(el, props);

		el.setSize = function(w,h) {

			el.style.width = w + "px"; 

			el.style.height = h + "px";

		};

		return el;

	}



	function canvas(w, h) {



    var c;

    c = element("canvas");

    c.width = w;

    c.height = h;



		var ctx = c.getContext("2d");



		var circleRads = Math.PI * 2;

		ctx.drawCircle = function(x, y, r) {

			ctx.arc(x, y, r, 0, circleRads, false);

		}



		return {

			canvas: c,

			ctx: ctx

		}

	}



	function button(txt, props) {

		props.innerHTML = txt;

		var b = element("div", props);

		return b;

	}





	function svg(tag, props) {

		var el = document.createElementNS("http://www.w3.org/2000/svg", tag);

		setAttributes(el, props);

		el.setSize = function(w,h) {

			el.setAttribute("width", w);

			el.setAttribute("height", h);

		};

		return el;

	}



	return {

		element: element,

		canvas: canvas,

		button: button,

		svg: svg

	}



})();











var limbs = {

	"body": {

		"range": 0,

		"baserot": 0,

		"length": 0,

		"offset": 0

	},

	"torso": {

		"range": 0,

		"baserot": Math.PI,

		"length": 100,

		"offset": 0

	},

	"thigh": {

		"range": 1,

		"baserot": 0.3,

		"length": 90,

		"offset": 0

	},

	"calf": {

		"range": -0.6,

		"baserot": -0.9,

		"length": 90,

		"offset": 1.6

	},

	"foot": {

		"range": 0.5,

		"baserot": 1.4,

		"length": 25,

		"offset": 0

	},

	"bicep": {

		"range": 0.9,

		"baserot": -0.5,

		"length": 70,

		"offset": 0

	},

	"forearm": {

		"range": 1.5,

		"baserot": 1.5,

		"length": 60,

		"offset": -0.3

	}

};



var iceskating = {"body":{"range":0,"baserot":0,"length":0,"offset":0},"torso":{"range":0,"baserot":2.5,"length":100,"offset":0},"thigh":{"range":0.5,"baserot":0.3,"length":100,"offset":0},"calf":{"range":-0.8,"baserot":-0.7,"length":110,"offset":2.9},"foot":{"range":0,"baserot":1.6,"length":40,"offset":0},"bicep":{"range":1.6,"baserot":-0.3,"length":85,"offset":0},"forearm":{"range":1.5,"baserot":1,"length":70,"offset":-0.3}};

//limbs = iceskating;



var human = [

	{name: "body", parent: null, movement: limbs.body, phase: 0},

	{name: "torso", parent: "body", movement: limbs.torso, phase: 0},

	{name: "thigh1", parent: "body", movement: limbs.thigh, phase: 0},

	{name: "calf1", parent: "thigh1", movement: limbs.calf, phase: 0},

	{name: "foot1", parent: "calf1", movement: limbs.foot, phase: 0},

	{name: "thigh2", parent: "body", movement: limbs.thigh, phase: Math.PI},

	{name: "calf2", parent: "thigh2", movement: limbs.calf, phase: Math.PI},

	{name: "foothigh2", parent: "calf2", movement: limbs.foot, phase: Math.PI},

	{name: "bicep1", parent: "torso", movement: limbs.bicep, phase: 0},

	{name: "forearm1", parent: "bicep1", movement: limbs.forearm, phase: 0},

	{name: "bicep2", parent: "torso", movement: limbs.bicep, phase: Math.PI},

	{name: "forearm2", parent: "bicep2", movement: limbs.forearm, phase: Math.PI},

];















var sw = 400;

var sh = 400;

var cx = sw / 2;

var cy = 0;

var horizon = sh - 50;

var blockSize = 10;

var creature = {};

var inputs = [];





function showRenderer(target) {

  var ids = ["jscanvas", "nested", "keyframes"];

  for (var i in ids) {

    var id = ids[i];

    document.getElementById(id).style.display = (id === target) ? "block" : "none";

    document.getElementById("button-" + id).className = (id === target) ? "button selected" : "button";

  }

}







function createButtons() {

	var buttonsRenderer = dom.element("div", {style: {position: "absolute", top: sh + 20 + "px"}});

	document.body.appendChild(buttonsRenderer);



	var buttonsEditToggle = dom.element("div", {style: {position: "absolute", top: sh + 70 + "px"}});

	document.body.appendChild(buttonsEditToggle);



  

	function createButton(parent, label, target, callback) {

		var button = dom.element("div", {id: "button-" + target, innerHTML: label, className: "button"});

		parent.appendChild(button);

		button.addEventListener("click", callback);

		return button;

	}

  

  

  createButton(buttonsRenderer, "JS + Canvas", "jscanvas", function() { showRenderer("jscanvas");});

	createButton(buttonsRenderer, "JS + Divs", "nested", function() { showRenderer("nested");});

	var b = createButton(buttonsRenderer, "CSS Keyframes + Divs", "keyframes", function() { showRenderer("keyframes");});

  b.className = "button selected";

  

  var editorOn = false;

  createButton(buttonsEditToggle, "Editor", "editor", function() {

    editorOn = !editorOn;

    document.getElementById("editor").style.display = editorOn ? "block" : "none";

    document.getElementById("output").style.display = editorOn ? "block" : "none";

    document.getElementById("button-editor").className = editorOn ? "button selected" : "button";

  });

  

  

}







function createEditor(limbs) {



	var editor = dom.element("div", {id: "editor", style: {color: "white","font-size":"10px", position: "absolute", top: 10 + "px", left: sw + "px", display: "none"}});

	var output = dom.element("pre", {id: "output", style: {position: "absolute", top: 0 + "px", left: 270 + "px", display: "none"}});



	function outputSettings() {

		output.innerHTML = "var limbs = " + JSON.stringify(limbs, null, "\t") + ";";

	};

	outputSettings();





	function edit(l,k) {

		var min = 0,  multiplier = 0.001, max = Math.PI * 2 / multiplier;

		if (k === "length") {

			max = 200;

			multiplier = 1;

		}



		var value = limbs[l][k];



		var edit = dom.element("div", {style: {margin: 2 + "px"}});

		var label = dom.element("div", {innerHTML: l + " " + k + ":", style: {display: "inline-block", textAlign: "right", width: 100 + "px"} });

		var input = dom.element("input", {value: value / multiplier, min: min, max: max, type: "range", style: {width: 100 + "px"}});

		var display = dom.element("input", {value: value, type: "number", style: {width: 50 + "px"}});



		editor.appendChild(edit);

		edit.appendChild(label);

		edit.appendChild(input);

		edit.appendChild(display);

		input.addEventListener("change", function(e) {

      showRenderer("jscanvas");

			var newValue = parseFloat(e.target.value) * multiplier;

			limbs[l][k] = newValue;

			display.value = newValue;

			outputSettings();

		})

		return input;

	}





	for (var l in limbs) {

		for (var k in limbs[l]) {

			inputs.push(edit(l,k));

		}

	}





	// var randomise = createButton("Random", function(e) {

	// 	for (var i in inputs) {

	// 		inputs[i].value = inputs[i].value * (0.8 + Math.random() * 0.4);

	// 		inputs[i].dispatchEvent(new Event('change'));

	// 	}

	// });



	// var morph = createButton("Morph", function(e) {

	// 	for (var i in inputs) {

	// 		inputs[i].newValue = inputs[i].value * (0.8 + Math.random() * 0.4);

	// 	}

	// });



	document.body.appendChild(editor);

	editor.appendChild(output);

}





function createLimbKeyframe(options) {

	var parent = options.parent;



	// con.log("createLimbKeyframe", options)



	var translationX = options.movement.length;

	var translationY = 0;

	var rotationStart = options.movement.baserot - options.movement.range;

	var rotationEnd = options.movement.baserot + options.movement.range;



	var divKeyframe = dom.element("div", {id: options.name, className: "limb"});

	var divJoint = dom.element("div", {id: options.name + "-joint", className: "joint"});



	if (parent) {

		parent.divJoint.appendChild(divKeyframe);

	} else {

		document.body.appendChild(divKeyframe);

	}

	divKeyframe.appendChild(divJoint);



	var transform = [

		"0% {transform: rotate(" + rotationStart + "rad);}",

		"50% {transform: rotate(" + rotationEnd + "rad);}",

		"100% {transform: rotate(" + rotationStart + "rad);}"

	];

	var transformJoint = [

		"0% {transform: translate(" + translationX + "px," + translationY + "px) rotate(" + -rotationStart + "rad);}",

	  "50% {transform: translate(" + translationX + "px," + translationY + "px) rotate(" + -rotationEnd + "rad);}",

	  "100% {transform: translate(" + translationX + "px," + translationY + "px) rotate(" + -rotationStart + "rad);}"

	];





	var time = 2;

	// 0.63 is a magic number - on my machine syncs up css keyframes with js.

	var delay = (-0.63 + -time * (options.movement.offset + options.phase) / (Math.PI * 2));

	var animation = options.name + "-animation " + time + "s " + delay + "s ease-in-out infinite;"

	var animationOpposite = options.name + "-joint-animation " + time + "s " + delay + "s ease-in-out infinite;"



	var css = [

	"#" + options.name + " {",

		"width: " + options.movement.length + "px;",

		"height: " + blockSize + "px;",

		"animation: " + animation,

		"-webkit-animation: " + animation,

	"};",

	"@keyframes " + options.name + "-animation {",

		transform.join(""),

	"}",

	"@-webkit-keyframes " + options.name + "-animation {",

		transform.join(""),

	"}",



	"#" + options.name + "-joint {",

		"animation: " + animationOpposite,

		"-webkit-animation: " + animationOpposite,

	"};",

	"@keyframes " + options.name + "-joint-animation {",

		transformJoint.join(""),

	"}",

	"@-webkit-keyframes " + options.name + "-joint-animation {",

		transformJoint.join(""),

	"}"



	];



	return {

		divKeyframe: divKeyframe,

		divJoint: divJoint,

		css: css,

	}



}













function createLimb(options) {



	var parent = options.parent;

	if (parent) {

		options.parent = creature[parent];

		parent = options.parent;

	}



	var div = {};

	div = dom.element("div", {style: {

		width: options.movement.length + "px",

		height: blockSize + "px",

		background: "rgba(255,0,0,0.5)",

		transformOrigin: "0 " + (blockSize / 2) + "px",

		position: "absolute"

	}});

	if (parent && parent.div) parent.div.appendChild(div);





	var keyframe = createLimbKeyframe(options),

		css = keyframe.css,

		divKeyframe = keyframe.divKeyframe,

		divJoint = keyframe.divJoint;



	return {

		name: options.name,

		options: options,

		div: div,



		css: css,

		divKeyframe: divKeyframe,

		divJoint: divJoint,



		pos: {

			sx: 0,

			sy: 0,

			ex: 0,

			ey: 0

		},

		calc: function(time) {

			var osc = options.movement.baserot + Math.sin(time + options.movement.offset + options.phase) * options.movement.range;

			this.position(osc);

			return Math.max(this.pos.sy, this.pos.ey);

		},

		position: function(osc) {



			this.osc = osc;



			var pos = {

				sx: 0,

				sy: 0,

				ex: 0 + Math.sin(osc) * options.movement.length,

				ey: 0 + Math.cos(osc) * options.movement.length

			}



			var translationX = 0, rotation = osc;



			if (options.parent) {

				var parent = options.parent;

				pos.sx += parent.pos.ex;

				pos.sy += parent.pos.ey;

				pos.ex += parent.pos.ex;

				pos.ey += parent.pos.ey;



				translationX = parent.options.movement.length;

				rotation = -parent.osc + osc;

			}



			this.translationX = translationX;

			this.rotationRad = rotation;

			this.pos = pos;

		},

		render: function(x, y) {

			ctx.beginPath();

			ctx.lineWidth = 3;

			ctx.strokeStyle = "#090";

			ctx.fillStyle = "#0a0";

			ctx.moveTo(x + this.pos.sx, y + this.pos.sy);

			ctx.lineTo(x + this.pos.ex, y + this.pos.ey);

			ctx.stroke();

			ctx.closePath();



			ctx.beginPath();

			ctx.drawCircle(x + this.pos.sx, y + this.pos.sy, blockSize / 2);

			ctx.drawCircle(x + this.pos.ex, y + this.pos.ey, blockSize / 2);

			ctx.closePath();

			ctx.fill();



			var parent = options.parent;

			var tx = parent ? this.translationX : y, ty = parent ? 0 : sw / 2;

			div.style.transform = "translate(" + tx + "px," + ty + "px)rotate(" + this.rotationRad + "rad)" ;

		}

	}

}







function render(t) {



	for (var i in inputs) {

		if (inputs[i].newValue && inputs[i].newValue != inputs[i].value) {

			inputs[i].value -= (inputs[i].value - inputs[i].newValue) * 0.1;

			inputs[i].dispatchEvent(new Event('change'));

		}

	}



	var time = t * Math.PI / 1000;



	ctx.fillStyle = "#030";

	ctx.fillRect(0, 0, sw, sh);

	ctx.fillStyle = "#060";

	ctx.fillRect(cx - sw / 2, horizon, sw, 10);



	// calculate impact with ground, ie maximum y position.

	// we can hope it's either the end of the calf or the end of the foot.

	// but walking on knees or head IS accepted.

	var max = 0;

	for (var l in creature) {

		max = Math.max(max, creature[l].calc(time)); // calculate each limb position

	}

	var x = cx, y = horizon - max - blockSize / 2;

	for (l in creature) {

		creature[l].render(x, y); // render each limb

	}



	requestAnimationFrame(render);



}























function init(settings, limbs) {



	bmp = dom.canvas(sw, sh);

	bmp.canvas.id = "jscanvas";

	bmp.canvas.style.display = "none";

	ctx = bmp.ctx;

	document.body.appendChild(bmp.canvas);



	creature = {};

	for (var c in settings) {

		var bit = settings[c];

		creature[bit.name] = createLimb(bit);

	}



	createEditor(limbs);



	createButtons();

  

	var divnested = dom.element("div", {id: "nested", style: {

		position: "absolute",

		display: "none",

		left: 0,

		top: 0,

		width: sh + "px",

		height: sw + "px",

		transform: "rotate(-90deg)scale(-1,1)",

		background: "rgba(255,0,0,0.2)"

	}});

	document.body.appendChild(divnested);

	divnested.appendChild(creature.body.div);



	var divKeyframes = dom.element("div", {id: "keyframes", style: {

		position: "absolute",

		// display: "none",

		top: 0,

		left: 0,

		width: sh + "px",

		height: sw + "px",

		transform: "rotate(-90deg)scale(-1,1)",

		background: "rgba(0,0,255,0.2)"

	}});

	document.body.appendChild(divKeyframes);

	divKeyframes.appendChild(creature.body.divKeyframe);



	var styleSheet = document.createElement("style");

	var css = [

		"body {",

			"overflow: auto;",

		"}",

		"#body {",

			"left: " + sh / 2 + "px;",

			"top: " + sw / 2 + "px;",

		"}",

		".limb {",

			"background: rgba(0,0,200,0.8);",

			"position: absolute;",

			"transform-origin: center left;",

			"transform-style: preserve-3d;",

		"}",

		".joint {",

			"background: rgba(0,0,255,0.9);",

			"height: 0;",

			"position: absolute;",

			"transform-origin: center center;",

			"width: 0;",

			// "transform: translateX(0px)",

		"}"

	];

	for (var l in creature) {

		css = css.concat(creature[l].css);

	}

	styleSheet.innerText = css.join(' ');

	// con.log(css);

	document.head.appendChild(styleSheet);





	render(0);

}









init(human, limbs);







  

</script>