cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              html, body, canvas {
  margin: 0px;
  height: 100%;
  width: 100%;
  overflow: hidden;
}
            
          
!
            
              (function () {

	var setting = {
      electronBaseSpeed: 0.05,
      electronRandomSpeed: 0.01,
      numberOfElectrons: 200,
      numberOfNeighbours: 15
    },
    // global variables
    canvas, ctx, width, height, ox, oy, mouseX = 0, mouseY = 0, 
    heart = [], electron = [];
  
  /*
    Heart data used from this 3d model found online. 
	  - http://www.gameartmarket.com/details?id=ag9zfmRhZGEtM2RtYXJrZXRyMgsSBFVzZXIiE3BpZXJ6YWtAcGllcnphay5jb20MCxIKVXNlclVwbG9hZBjX1sjl-ScM
    I added a few points and scaled the heart along one dimension to make it less 'fat' than before.
    This string of numbers is the serialized point data from an .X3D export using blender.
  */
	var pointData = "32.7292 25.3718 43.2941 21.0824 36.5664 57.2912 0 27.3696 39.0791 0 26.9858 60.5122 0 2.32386 60.836 23.9945 4.97553 43.9781 23.9945 4.7544 56.3249 21.0824 36.8403 41.9962 32.7292 25.1378 56.3585 16.7785 -5.49356 50.3167 8.93792 37.3095 49.634 18.8047 18.6138 37.4886 8.38923 -6.68778 55.9632 1.51521 -17.7438 50.5677 34.0204 16.3753 49.9679 8.38923 -6.48615 44.7051 18.8047 18.1679 62.3829 32.5001 34.5348 49.6783 30.2165 17.023 41.9277 14.6811 -5.83143 53.9235 10.3525 34.4104 60.2188 5.58722 -13.1681 50.4391 36.2585 26.7705 49.8021 10.3525 34.788 39.1357 0 29.5792 49.7573 0 13.515 63.157 14.6811 -5.70239 46.7186 30.2165 16.7353 57.9921 27.2874 5.05264 50.1485 14.116 4.47563 40.4627 14.116 4.12825 59.8583 28.9508 30.6739 39.9656 28.9508 30.3237 59.5197 22.3116 40.7303 49.5795 -14.6811 -5.70239 46.7186 -16.7785 -5.49356 50.3167 -5.58722 -13.1681 50.4391 -23.9945 4.97553 43.9781 -27.2874 5.05264 50.1485 -23.9945 4.7544 56.3249 -14.6811 -5.83143 53.9235 -21.6175 37.3353 41.9883 -10.3525 34.788 39.1357 -8.93792 37.3095 49.634 -22.8466 41.2253 49.5716 -10.3525 34.4104 60.2188 -21.6175 37.0614 57.2833 -18.8047 18.6138 37.4886 0 13.986 36.8626 -28.9508 30.6739 39.9656 -32.7292 25.3718 43.2941 -30.2165 17.023 41.9277 -14.116 4.47563 40.4627 0 2.70524 39.542 0 -19.2088 50.6313 -1.51521 -17.7438 50.5677 -8.38923 -6.68778 55.9632 0 -7.24557 56.5913 -14.116 4.12825 59.8583 0 -7.02175 44.0944 -8.38923 -6.48615 44.7051 -34.0204 16.3753 49.9679 -36.2585 26.7705 49.8021 -32.7292 25.1378 56.3585 -30.2165 16.7353 57.9921 -18.8047 18.1679 62.3829 -28.9508 30.3237 59.5197 -32.5001 34.5348 49.6783"
	;
	
	// create Math.sin lookup table (cos == sin+90)
	var r = [];
	for (var i = 0; i < 720; i++)
		r.push(Math.sin(i * (Math.PI / 180)));

	// rotate a point about origin in 3d space
	function rotate(p, x, y, z) {
		var u, v, w, h = p.x * r[z+90] - p.y * r[z];
		z = p.x * r[z] + p.y * r[z+90];
		u = p.z * r[y+90] - h * r[y];
		v = p.z * r[y] + h * r[y+90];
		h = z * r[x+90] - u * r[x];
		w = z * r[x] + u * r[x+90];
		return { x: v, y: h, z: w, p: p.p };
	};
  
  // find the closest neighbours in the network to the specified point
  function findNeighbours(p, data) {
    var t = [];
    for (var i = 0; i < data.length; i++)
      t.push({
        i: i,
        d: distanceBetweenPoints(data[i], data[p])
      });
    t.sort(function(p1, p2) {
			return p1.d - p2.d;
		});
    return t.slice(1, 1 + setting.numberOfNeighbours).map(function(e) {
      return e.i;
    });
  }
  
  function nthroot(x, n) { 
    // This function borrowed from http://stackoverflow.com/questions/7308627/javascript-calculate-the-nth-root-of-a-number
    try {
      var negate = n % 2 == 1 && x < 0;
      if(negate)
        x = -x;
      var possible = Math.pow(x, 1 / n);
      n = Math.pow(possible, n);
      if(Math.abs(x - n) < 1 && (x > 0 == n > 0))
        return negate ? -possible : possible;
    } catch(e) {}
}
  
  function distanceBetweenPoints(p1, p2) {
    return nthroot(
      (
        (p2.x - p1.x) * (p2.x - p1.x) + 
        (p2.y - p1.y) * (p2.y - p1.y) + 
        (p2.z - p1.z) * (p2.z - p1.z)
      ), 3);
  }
  
	function deserializeHeartData() {
		var d = [],
			r = pointData.split(' ');
			
		function roundScalar(p) {
			return Math.round(parseFloat(p) * 8);
		}
		
		for (var i = 0, j = 0; i < r.length; j++) {
			var t = {
				x: roundScalar(r[i++]),
				y: roundScalar(r[i++]) - 115, // these constants to center the object
				z: roundScalar(r[i++]) - 400
			};
      d.push(t);
		}
    
    for (var i = 0; i < d.length; i++)
      d[i].p = {
        i: i,
        n: findNeighbours(i, d)
      };
		
		return d;
	}
  
	function init() {
		// create canvas
		canvas = document.createElement('canvas');
		height = canvas.height = document.body.clientHeight - 1;
		width = canvas.width = document.body.clientWidth - 1;
    // setup origin
		ox = width / 2;
		oy = height / 2;
    // append to DOM and set some defaults
		document.body.appendChild(canvas);
		ctx = canvas.getContext('2d');
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;
    // initial instant paint of background
		ctx.fillStyle = 'rgba(0,0,0,1)';
		ctx.fillRect(0, 0, width, height);

		// deserialize heart from X3D model data
		heart = deserializeHeartData();
		    
    for (var i = 0; i < setting.numberOfElectrons; i++) {
      var j = ~~(Math.random() * heart.length);
      electron.push({ p1: j, p2: heart[j].p.n[0], d: 0, s: Math.random() * setting.electronBaseSpeed + setting.electronRandomSpeed });
    }
    
		// start our animation
    window.requestAnimationFrame(render);
	}
  
  function distanceOpacity(z, zmin, zmax) {
    return Math.min(1, Math.max(0, (z - zmin) / (zmax - zmin)));
  }
	
	function render() {
    ctx.shadowColor = 'rgba(0,0,0,0)';
		ctx.fillStyle = 'rgba(0,0,0,0.5)';
		ctx.fillRect(0, 0, width, height);
		
		// create new 3d point array
		var rotated = [];
		
		// populate array with data from initial state, but rotated required amount
		for (i = 0; i < heart.length; i++) {
			var f = rotate(heart[i], ~~(180 - 70 * mouseY), ~~(180 - 50 * mouseX), 0);
			rotated.push(f);
		}
		
		// sort the points by z-value
		rotated.sort(function(p1, p2) {
			return p1.z - p2.z;
		});
    
    // update mapping to renderable from the original data
    for (i = 0; i < rotated.length; i++) {
      heart[rotated[i].p.i].p.o = i;
    }    
		
    for (i = 0; i < electron.length; i++) {
      var p1 = rotated[heart[electron[i].p1].p.o],
          p2 = rotated[heart[electron[i].p2].p.o],
          ex = ((p2.x - p1.x) * electron[i].d) + p1.x,
          ey = ((p2.y - p1.y) * electron[i].d) + p1.y,
          ez = ((p2.z - p1.z) * electron[i].d) + p1.z;
			var rad = 6;
			ctx.beginPath();
      ctx.shadowColor = 'rgba(255,80,80,' + distanceOpacity(ez, -200, 100) + ')';
			ctx.arc(ox + ex, oy + ey, rad, 0, Math.PI *  2, false);
			ctx.closePath();
      var radial = ctx.createRadialGradient(ox + ex, oy + ey, 0, ox + ex, oy + ey, rad);
      radial.addColorStop(0,'rgba(255,120,220,' + distanceOpacity(ez, -200, 220) + ')');
      radial.addColorStop(0.6,'rgba(145,0,100,' + distanceOpacity(ez, -200, 220) + ')');
      radial.addColorStop(1,'rgba(40,0,0,' + distanceOpacity(ez, -200, 480) + ')');
      ctx.fillStyle = radial;
      ctx.fill();
      electron[i].d += electron[i].s;
      if (electron[i].d >= 1) {
        electron[i].d = 0;
        electron[i].p1 = electron[i].p2;
        electron[i].p2 = heart[electron[i].p2].p.n[
          ~~(heart[electron[i].p2].p.n.length * Math.random())
        ];
        electron[i].s = Math.random() * setting.electronBaseSpeed + setting.electronRandomSpeed;
      }
    } 
		
		// render the points, starting furthest to closest
    ctx.shadowBlur = 33;
    for (i = 0; i < rotated.length; i++) {
			var rad = 15;
			ctx.beginPath();
      ctx.shadowColor = 'rgba(255,80,80,' + distanceOpacity(rotated[i].z, -200, 100) + ')';
			ctx.arc(ox + rotated[i].x, oy + rotated[i].y, rad, 0, Math.PI *  2, false);
			ctx.closePath();
      var radial = ctx.createRadialGradient(ox + rotated[i].x, oy + rotated[i].y, 0, ox + rotated[i].x, oy + rotated[i].y, rad);
      radial.addColorStop(0,'rgba(255,20,20,' + distanceOpacity(rotated[i].z, -200, 220) + ')');
      radial.addColorStop(0.6,'rgba(245,0,0,' + distanceOpacity(rotated[i].z, -200, 220) + ')');
      radial.addColorStop(1,'rgba(100,0,0,' + distanceOpacity(rotated[i].z, -200, 380) + ')');
      ctx.fillStyle = radial;
      ctx.fill();
    }

	  ctx.font='142px x';
		ctx.fillStyle='#fff';
		ctx.fillText('愛老虎油' , ox - 270, oy + 230);
    
    window.requestAnimationFrame(render);
	}
	
	window.onload = init;
  
  window.onmousemove = function(e) {
		mouseX = (e.pageX - ox) / width;
		mouseY = (e.pageY - oy) / height;
	};

})();
            
          
!
999px
Loading ..................

Console