A study creating performant particles with Canvas 2D
A Pen by Justin Windle on CodePen.
A study creating performant particles with Canvas 2D
A Pen by Justin Windle on CodePen.
| #container | |
| .info | |
| %hgroup.about | |
| %h1 30,000 Particles | |
| %h2 A study creating performant particles with Canvas 2D | |
| %h3 Use your mouse |
| var NUM_PARTICLES = ( ( ROWS = 100 ) * ( COLS = 300 ) ), | |
| THICKNESS = Math.pow( 80, 2 ), | |
| SPACING = 3, | |
| MARGIN = 100, | |
| COLOR = 220, | |
| DRAG = 0.95, | |
| EASE = 0.25, | |
| /* | |
| used for sine approximation, but Math.sin in Chrome is still fast enough :)http://jsperf.com/math-sin-vs-sine-approximation | |
| B = 4 / Math.PI, | |
| C = -4 / Math.pow( Math.PI, 2 ), | |
| P = 0.225, | |
| */ | |
| container, | |
| particle, | |
| canvas, | |
| mouse, | |
| stats, | |
| list, | |
| ctx, | |
| tog, | |
| man, | |
| dx, dy, | |
| mx, my, | |
| d, t, f, | |
| a, b, | |
| i, n, | |
| w, h, | |
| p, s, | |
| r, c | |
| ; | |
| particle = { | |
| vx: 0, | |
| vy: 0, | |
| x: 0, | |
| y: 0 | |
| }; | |
| function init() { | |
| container = document.getElementById( 'container' ); | |
| canvas = document.createElement( 'canvas' ); | |
| ctx = canvas.getContext( '2d' ); | |
| man = false; | |
| tog = true; | |
| list = []; | |
| w = canvas.width = COLS * SPACING + MARGIN * 2; | |
| h = canvas.height = ROWS * SPACING + MARGIN * 2; | |
| container.style.marginLeft = Math.round( w * -0.5 ) + 'px'; | |
| container.style.marginTop = Math.round( h * -0.5 ) + 'px'; | |
| for ( i = 0; i < NUM_PARTICLES; i++ ) { | |
| p = Object.create( particle ); | |
| p.x = p.ox = MARGIN + SPACING * ( i % COLS ); | |
| p.y = p.oy = MARGIN + SPACING * Math.floor( i / COLS ); | |
| list[i] = p; | |
| } | |
| container.addEventListener( 'mousemove', function(e) { | |
| bounds = container.getBoundingClientRect(); | |
| mx = e.clientX - bounds.left; | |
| my = e.clientY - bounds.top; | |
| man = true; | |
| }); | |
| if ( typeof Stats === 'function' ) { | |
| document.body.appendChild( ( stats = new Stats() ).domElement ); | |
| } | |
| container.appendChild( canvas ); | |
| } | |
| function step() { | |
| if ( stats ) stats.begin(); | |
| if ( tog = !tog ) { | |
| if ( !man ) { | |
| t = +new Date() * 0.001; | |
| mx = w * 0.5 + ( Math.cos( t * 2.1 ) * Math.cos( t * 0.9 ) * w * 0.45 ); | |
| my = h * 0.5 + ( Math.sin( t * 3.2 ) * Math.tan( Math.sin( t * 0.8 ) ) * h * 0.45 ); | |
| } | |
| for ( i = 0; i < NUM_PARTICLES; i++ ) { | |
| p = list[i]; | |
| d = ( dx = mx - p.x ) * dx + ( dy = my - p.y ) * dy; | |
| f = -THICKNESS / d; | |
| if ( d < THICKNESS ) { | |
| t = Math.atan2( dy, dx ); | |
| p.vx += f * Math.cos(t); | |
| p.vy += f * Math.sin(t); | |
| } | |
| p.x += ( p.vx *= DRAG ) + (p.ox - p.x) * EASE; | |
| p.y += ( p.vy *= DRAG ) + (p.oy - p.y) * EASE; | |
| } | |
| } else { | |
| b = ( a = ctx.createImageData( w, h ) ).data; | |
| for ( i = 0; i < NUM_PARTICLES; i++ ) { | |
| p = list[i]; | |
| b[n = ( ~~p.x + ( ~~p.y * w ) ) * 4] = b[n+1] = b[n+2] = COLOR, b[n+3] = 255; | |
| } | |
| ctx.putImageData( a, 0, 0 ); | |
| } | |
| if ( stats ) stats.end(); | |
| requestAnimationFrame( step ); | |
| } | |
| init(); | |
| step(); |
| <script src="https://gist.github.com/mrdoob/838785/raw/a19a753b441d6ad41707c58f06dbe17f3470423c/RequestAnimationFrame.js"></script> | |
| <script src="https://raw.github.com/mrdoob/stats.js/master/build/stats.min.js"></script> |
| @import compass | |
| html, body | |
| background: #111 | |
| #container | |
| background: #111 | |
| position: absolute | |
| left: 50% | |
| top: 50% | |
| #stats | |
| position: absolute | |
| right: 10px | |
| top: 10px | |
| /* Info */ | |
| @import url( https://fonts.googleapis.com/css?family=Quantico ) | |
| @keyframes show-info | |
| 0% | |
| transform: rotateY(120deg) | |
| 100% | |
| transform: rotateY(0deg) | |
| .info | |
| transition: all 180ms ease-out | |
| transform-style: preserve-3d | |
| transform: perspective(800px) | |
| font-family: 'Quantico', sans-serif | |
| position: absolute | |
| font-size: 12px | |
| opacity: 0.8 | |
| color: #fff | |
| width: 220px | |
| left: 0px | |
| top: 20px | |
| &:hover | |
| box-shadow: 0 0 0 4px rgba(255,255,255,0.05) | |
| opacity: 1.0 | |
| h1, h2, h3 | |
| line-height: 1 | |
| margin: 5px 0 | |
| a | |
| transition: all 200ms ease-out | |
| border-bottom: 1px dotted rgba(255,255,255,0.4) | |
| text-decoration: none | |
| opacity: 0.6 | |
| color: #fff | |
| &:hover | |
| opacity: 0.99 | |
| .about, | |
| .more | |
| transform-origin: 0% 50% | |
| transform: rotateY(120deg) | |
| margin-bottom: 1px | |
| background: rgba(0,0,0,0.8) | |
| padding: 12px 15px 12px 20px | |
| .about | |
| animation: show-info 500ms cubic-bezier(0.230, 1.000, 0.320, 1.000) 600ms 1 normal forwards | |
| padding-bottom: 15px | |
| a | |
| opacity: 0.9 | |
| h1 | |
| letter-spacing: -1px | |
| font-weight: 300 | |
| font-size: 19px | |
| opacity: 0.95 | |
| h2 | |
| font-weight: 300 | |
| font-size: 13px | |
| opacity: 0.8 | |
| h3 | |
| text-transform: uppercase | |
| margin-top: 10px | |
| font-size: 11px | |
| &:before | |
| margin-right: 2px | |
| font-size: 14px | |
| content: '\203A' | |
| .more | |
| animation: show-info 500ms cubic-bezier(0.230, 1.000, 0.320, 1.000) 500ms 1 normal forwards | |
| padding: 5px 15px 10px 20px | |
| a | |
| text-transform: uppercase | |
| margin-right: 10px | |
| font-size: 10px |