61 | An experimental CSS 3D fold-in menu. Works in any browser that supports CSS 3D transforms, that includes Mobile Safari so get your iPhone ready!
62 |
70 | Decided it was time for some CSS tinkering again and ended up creating this set of CSS3 scrolling styles. Not intended for any practical use but the visuals are surprisingly impactful.
71 |
72 |
73 | This works by applying a
74 | future/past
75 | class to list items outside of the viewport as you scroll. Based on this class a variety of transforms are transitioned to via CSS.
76 |
77 |
78 | Thanks
79 | Paul Irish
80 | for improving the JavaScript performance!
81 |
121 | Almost two years after releasing the
122 | web version
123 | Sinuous has finally made its way to the App Store! The gameplay is very much alike the web but it ties into Game Center so that you can compete with friends. Oh, and keep and eye our for the new 'vortex'-boost!
124 |
125 |
126 | The game is still powered by JavaScript and rendered on HTML canvas. It relies on native code for audio and Game Center integration but that bit was easy thanks to the ever so lovely
127 | PhoneGap. I'm very happy with how well the controls translated to touch and I hope you will be too.
128 |
129 |
130 | This runs as smooth as butter on the iPad but the framerate is a bit shaky on the iPhone 4. I will be releasing an update soon to address that as well as some other improvements.
131 | Try it out!
132 |
141 | An experimental animated ribbon which lets you drag down a curtain of additional content. A fun twist on the "Fork me on GitHub" banner! Created with JavaScript-controlled CSS3 transforms.
142 |
150 | As
151 | reveal.js
152 | has grown more popular there has been a lot of requests for an online editor and
153 | rvl.io
154 | is the answer. rvl.io is a service for authoring, storing and presenting slide decks without having to touch the underlying HTML of reveal.js. It was created by myself (frontend) and
155 | @h__r__j
156 | (backend).
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/js/fokus.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Fokus 0.5
3 | * http://lab.hakim.se/fokus
4 | * MIT licensed
5 | *
6 | * Copyright (C) 2012 Hakim El Hattab, http://hakim.se
7 | */
8 |
9 | (function(){
10 |
11 | // Padding around the selection
12 | var PADDING = 5;
13 |
14 | // Opacity of the overlay
15 | var OPACITY = 0.75;
16 |
17 | // Key modifier that needs to be held down for overlay to appear
18 | var MODIFIER = null;
19 |
20 | // The opaque overlay canvas
21 | var overlay,
22 | overlayContext,
23 | overlayAlpha = 0,
24 |
25 | // Reference to the redraw animation so it can be cancelled
26 | redrawAnimation,
27 |
28 | // Currently selected region
29 | selectedRegion = { left: 0, top: 0, right: 0, bottom: 0 },
30 |
31 | // Currently cleared region
32 | clearedRegion = { left: 0, top: 0, right: 0, bottom: 0 },
33 |
34 | // Currently pressed down key modifiers
35 | keyModifiers = { ctrl: false, shift: false, alt: false, cmd: false };
36 |
37 | // choo choo!
38 | function initialize() {
39 |
40 | // Only initialize if the client is capable
41 | if( capable() && !window.__fokused ) {
42 |
43 | // Ensures that Fokus isn't initialized twice on the same page
44 | window.__fokused = true;
45 |
46 | overlay = document.createElement( 'canvas' );
47 | overlayContext = overlay.getContext( '2d' );
48 |
49 | // Place the canvas on top of everything
50 | overlay.style.position = 'fixed';
51 | overlay.style.left = 0;
52 | overlay.style.top = 0;
53 | overlay.style.zIndex = 2147483647;
54 | overlay.style.pointerEvents = 'none';
55 | overlay.style.background = 'transparent';
56 |
57 | document.addEventListener( 'mousedown', onMouseDown, false );
58 | document.addEventListener( 'keyup', onKeyPress, false );
59 | document.addEventListener( 'keydown', onKeyPress, false );
60 | document.addEventListener( 'scroll', onScroll, false );
61 | document.addEventListener( 'DOMMouseScroll', onScroll, false );
62 | window.addEventListener( 'resize', onWindowResize, false );
63 |
64 | // Trigger an initial resize
65 | onWindowResize();
66 |
67 | }
68 |
69 | }
70 |
71 | /**
72 | * Is this browser capable of running Fokus?
73 | */
74 | function capable() {
75 |
76 | return !!(
77 | 'addEventListener' in document &&
78 | 'pointerEvents' in document.body.style
79 | );
80 |
81 | }
82 |
83 | /**
84 | * Redraws an animates the overlay.
85 | */
86 | function redraw() {
87 |
88 | // Cache the response of this for re-use below
89 | var _hasSelection = hasSelection();
90 |
91 | // Reset to a solid (less opacity) overlay fill
92 | overlayContext.clearRect( 0, 0, overlay.width, overlay.height );
93 | overlayContext.fillStyle = 'rgba( 0, 0, 0, '+ overlayAlpha +' )';
94 | overlayContext.fillRect( 0, 0, overlay.width, overlay.height );
95 |
96 | if( _hasSelection ) {
97 | if( overlayAlpha < 0.1 ) {
98 | // Clear the selection instantly if we're just fading in
99 | clearedRegion = selectedRegion;
100 | }
101 | else {
102 | // Ease the cleared region towards the selected selection
103 | clearedRegion.left += ( selectedRegion.left - clearedRegion.left ) * 0.18;
104 | clearedRegion.top += ( selectedRegion.top - clearedRegion.top ) * 0.18;
105 | clearedRegion.right += ( selectedRegion.right - clearedRegion.right ) * 0.18;
106 | clearedRegion.bottom += ( selectedRegion.bottom - clearedRegion.bottom ) * 0.18;
107 | }
108 | }
109 |
110 | // Cut out the cleared region
111 | overlayContext.clearRect(
112 | clearedRegion.left - window.scrollX - PADDING,
113 | clearedRegion.top - window.scrollY - PADDING,
114 | ( clearedRegion.right - clearedRegion.left ) + ( PADDING * 2 ),
115 | ( clearedRegion.bottom - clearedRegion.top ) + ( PADDING * 2 )
116 | );
117 |
118 | // Fade in if there's a valid selection...
119 | if( _hasSelection ) {
120 | overlayAlpha += ( OPACITY - overlayAlpha ) * 0.08;
121 | }
122 | // ... otherwise fade out
123 | else {
124 | overlayAlpha = Math.max( ( overlayAlpha * 0.85 ) - 0.02, 0 );
125 | }
126 |
127 | // Ensure there is no overlap
128 | cancelAnimationFrame( redrawAnimation );
129 |
130 | // Continue so long as there is content selected or we are fading out
131 | if( _hasSelection || overlayAlpha > 0 ) {
132 | // Append the overlay if it isn't already in the DOM
133 | if( !overlay.parentNode ) document.body.appendChild( overlay );
134 |
135 | // Stage a new animation frame
136 | redrawAnimation = requestAnimationFrame( redraw );
137 | }
138 | else {
139 | document.body.removeChild( overlay );
140 | }
141 |
142 | }
143 |
144 | /**
145 | * Steps through all selected nodes and updates the selected
146 | * region (bounds of selection).
147 | *
148 | * @param {Boolean} immediate flags if selection should happen
149 | * immediately, defaults to false which means the selection
150 | * rect animates into place
151 | */
152 | function updateSelection( immediate ) {
153 |
154 | // Default to negative space
155 | var currentRegion = { left: Number.MAX_VALUE, top: Number.MAX_VALUE, right: 0, bottom: 0 };
156 |
157 | var nodes = getSelectedNodes();
158 |
159 | for( var i = 0, len = nodes.length; i < len; i++ ) {
160 | var node = nodes[i];
161 |
162 | // Select parents of text nodes that have contents
163 | if( node.nodeName === '#text' && node.nodeValue.trim() ) {
164 | node = node.parentNode;
165 | }
166 |
167 | // Fetch the screen coordinates for this element
168 | var position = getScreenPosition( node );
169 |
170 | var x = position.x,
171 | y = position.y,
172 | w = node.offsetWidth,
173 | h = node.offsetHeight;
174 |
175 | // 1. offsetLeft works
176 | // 2. offsetWidth works
177 | // 3. Element is larger than zero pixels
178 | // 4. Element is not
179 | if( node && typeof x === 'number' && typeof w === 'number' && ( w > 0 || h > 0 ) && !node.nodeName.match( /^br$/gi ) ) {
180 | currentRegion.left = Math.min( currentRegion.left, x );
181 | currentRegion.top = Math.min( currentRegion.top, y );
182 | currentRegion.right = Math.max( currentRegion.right, x + w );
183 | currentRegion.bottom = Math.max( currentRegion.bottom, y + h );
184 | }
185 | }
186 |
187 | // Don't update selection if a modifier is specified but not
188 | // pressed down, unless there's already a selected region
189 | if( !MODIFIER || MODIFIER === 'none' || keyModifiers[ MODIFIER ] || hasSelection() ) {
190 | selectedRegion = currentRegion;
191 | }
192 |
193 | // If flagged, update the cleared region immediately
194 | if( immediate ) {
195 | clearedRegion = selectedRegion;
196 | }
197 |
198 | // Start repainting if there is a selected region
199 | if( hasSelection() ) {
200 | redraw();
201 | }
202 |
203 | }
204 |
205 | /**
206 | * Checks if a region is currently selected.
207 | */
208 | function hasSelection() {
209 |
210 | return selectedRegion.left < selectedRegion.right && selectedRegion.top < selectedRegion.bottom;
211 |
212 | }
213 |
214 | function onMouseDown( event ) {
215 |
216 | // Don't start selection on right click
217 | if( event.which !== 3 ) {
218 | document.addEventListener( 'mousemove', onMouseMove, false );
219 | document.addEventListener( 'mouseup', onMouseUp, false );
220 |
221 | updateSelection();
222 | }
223 |
224 | }
225 |
226 | function onMouseMove( event ) {
227 |
228 | updateSelection();
229 |
230 | }
231 |
232 | function onMouseUp( event ) {
233 |
234 | document.removeEventListener( 'mousemove', onMouseMove, false );
235 | document.removeEventListener( 'mouseup', onMouseUp, false );
236 |
237 | setTimeout( updateSelection, 1 );
238 |
239 | }
240 |
241 | function onKeyPress( event ) {
242 | keyModifiers.alt = event.altKey || event.altGraphKey;
243 | keyModifiers.ctrl = event.ctrlKey;
244 | keyModifiers.shift = event.shiftKey;
245 | keyModifiers.meta = event.metaKey;
246 |
247 | updateSelection();
248 |
249 | }
250 |
251 | function onScroll( event ) {
252 |
253 | updateSelection( true );
254 |
255 | }
256 |
257 | /**
258 | * Make sure the overlay canvas is always as wide and tall as
259 | * the current window.
260 | */
261 | function onWindowResize( event ) {
262 |
263 | overlay.width = window.innerWidth;
264 | overlay.height = window.innerHeight;
265 |
266 | }
267 |
268 | /**
269 | * Helper methods for getting selected nodes, source:
270 | * http://stackoverflow.com/questions/7781963/js-get-array-of-all-selected-nodes-in-contenteditable-div
271 | */
272 | function getSelectedNodes() {
273 |
274 | if (window.getSelection) {
275 | var sel = window.getSelection();
276 | if (!sel.isCollapsed) {
277 | return getRangeSelectedNodes(sel.getRangeAt(0));
278 | }
279 | }
280 | return [];
281 |
282 | }
283 | function getRangeSelectedNodes( range ) {
284 |
285 | var node = range.startContainer;
286 | var endNode = range.endContainer;
287 |
288 | // Special case for a range that is contained within a single node
289 | if (node == endNode) {
290 | if( node.nodeName === '#text' ) {
291 | return [node.parentNode];
292 | }
293 | return [node];
294 | }
295 |
296 | // Iterate nodes until we hit the end container
297 | var rangeNodes = [];
298 | while (node && node != endNode) {
299 | rangeNodes.push( node = nextNode(node) );
300 | }
301 |
302 | // Add partially selected nodes at the start of the range
303 | node = range.startContainer;
304 | while (node && node != range.commonAncestorContainer) {
305 | rangeNodes.unshift(node);
306 | node = node.parentNode;
307 | }
308 |
309 | return rangeNodes;
310 |
311 | }
312 | function nextNode(node) {
313 |
314 | if (node.hasChildNodes()) {
315 | return node.firstChild;
316 | } else {
317 | while (node && !node.nextSibling) {
318 | node = node.parentNode;
319 | }
320 | if (!node) {
321 | return null;
322 | }
323 | return node.nextSibling;
324 | }
325 |
326 | }
327 |
328 | /**
329 | * Gets the x/y screen position of the target node, source:
330 | * http://www.quirksmode.org/js/findpos.html
331 | */
332 | function getScreenPosition( node ) {
333 | var x = document.documentElement.offsetLeft,
334 | y = document.documentElement.offsetTop;
335 |
336 | if ( node.offsetParent ) {
337 | do {
338 | x += node.offsetLeft;
339 | y += node.offsetTop;
340 | } while ( node = node.offsetParent );
341 | }
342 |
343 | return { x: x, y: y };
344 | }
345 |
346 | /**
347 | * rAF polyfill.
348 | */
349 | (function() {
350 | var lastTime = 0;
351 | var vendors = ['ms', 'moz', 'webkit', 'o'];
352 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
353 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
354 | window.cancelAnimationFrame =
355 | window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
356 | }
357 |
358 | if (!window.requestAnimationFrame)
359 | window.requestAnimationFrame = function(callback, element) {
360 | var currTime = new Date().getTime();
361 | var timeToCall = Math.max(0, 16 - (currTime - lastTime));
362 | var id = window.setTimeout(function() { callback(currTime + timeToCall); },
363 | timeToCall);
364 | lastTime = currTime + timeToCall;
365 | return id;
366 | };
367 |
368 | if (!window.cancelAnimationFrame)
369 | window.cancelAnimationFrame = function(id) {
370 | clearTimeout(id);
371 | };
372 | }());
373 |
374 | initialize();
375 |
376 | })();
377 |
--------------------------------------------------------------------------------
/js/fokus.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Fokus 0.5
3 | * http://lab.hakim.se/fokus
4 | * MIT licensed
5 | *
6 | * Copyright (C) 2012 Hakim El Hattab, http://hakim.se
7 | */
8 | (function(){function u(){var m=s();l.clearRect(0,0,a.width,a.height);l.fillStyle="rgba( 0, 0, 0, "+d+" )";l.fillRect(0,0,a.width,a.height);m&&(0.1>d?b=h:(b.left+=0.18*(h.left-b.left),b.top+=0.18*(h.top-b.top),b.right+=0.18*(h.right-b.right),b.bottom+=0.18*(h.bottom-b.bottom)));l.clearRect(b.left-window.scrollX-q,b.top-window.scrollY-q,b.right-b.left+2*q,b.bottom-b.top+2*q);d=m?d+0.08*(B-d):Math.max(0.85*d-0.02,0);cancelAnimationFrame(v);m||0