Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

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.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
              
            
!

CSS

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

JS

              
                (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

Console