tag:blogger.com,1999:blog-39113372150746442902024-02-23T23:43:47.827-07:00UI Hacker - Code for FunA collection of handy code snippets in the languages that I use on a daily basis.justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.comBlogger88125tag:blogger.com,1999:blog-3911337215074644290.post-48956735420591398352014-10-26T19:53:00.000-06:002014-10-26T19:55:14.057-06:00JavaScript: Use two.js to prepare svg shapes for a PIXI.js SpriteWhen creating <a href="http://www.pixijs.com/">PIXI.js</a> Sprites from vector SVG shapes, you may want to scale up the SVG before turning it into a bitmap sprite, in order to get the best resolution for your screen. Here's a little snippet that will create a <a href="http://jonobr1.github.io/two.js/">two.js</a> canvas to do just that, and return a PIXI.Sprite with your ideal bitmap size:
<pre>
// init Two for svg import
Two.Resolution = 24;
var _twoCanvas = new Two({
width: 400,
height: 400,
type: Two.Types.canvas
});
// create method to read an svg element from the DOM, scale it, and return a PIXI.Sprite
var getScaledSpriteFromSVG = function(elemId, scale) {
_twoCanvas.clear();
// import svg from DOM, scale up, fit canvas to svg and render!
var shape = _twoCanvas.interpret(document.getElementById(elemId));
shape.scale = scale;
var charH = Math.ceil(shape.getBoundingClientRect().height);
var charW = Math.ceil(shape.getBoundingClientRect().width);
_twoCanvas.width = charW;
_twoCanvas.height = charH;
_twoCanvas.update();
// grab two.js canvas contents by exporting its base64 png content
var newSvgSprite = new PIXI.Sprite(PIXI.Texture.fromImage(_twoCanvas.renderer.domElement.toDataURL()));
newSvgSprite.anchor.x = 0.5;
newSvgSprite.anchor.y = 0.5;
return newSvgSprite;
};
var birdieSvgSprite = getScaledSpriteFromSVG('birdie', 2);
</pre>
And this should be in your DOM, which could be hidden via css (display:none;):
<pre>
<svg id="birdie">-svg content here-</svg>
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-944689278820469392014-10-26T16:14:00.001-06:002014-10-26T16:14:17.231-06:00JavaScript: Automatically scroll to the end of an infinitely-scrolling pageSimply paste this code into your browser console:
<pre>
var scrollInterval = setInterval(function(){window.scrollTo(0,-999999999999)},500)
</pre>
When it's done, clear out the auto-scroll by pasting this code into your console:
<pre>
clearInterval(scrollInterval)
</pre>
And if, for some reason, the page wants to log you out (in my case I was trying to load all of my "Friends" on Facebook so I could mass-unfriend people), paste the following code into the console to prevent the page from reloading:
<pre>
window.addEventListener('beforeunload',function(e){e.returnValue = 'sure?';return 'sure?'});
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-91959469665538528002014-05-08T08:42:00.000-06:002014-05-09T18:08:29.736-06:00Return to the beginning of an array with a ternary operatorOften times when iterating and looping over the contents of an array, I want to set the current index to zero if it's reached the last element. Generally this happens in the form of an if/else statement, but I enjoy using a more terse form with the help of a ternary operator. See the code below, and enjoy!
<pre>
var myArray = ['one', 'two', 'three', 'four'];
var curIndex = 0;
setInterval(function(){
console.log(myArray[curIndex]);
curIndex = (curIndex < myArray.length - 1) ? curIndex + 1 : 0;
}, 500);
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-34248485894147842812013-08-16T16:08:00.002-06:002013-08-16T16:08:30.050-06:00iOS: Force audio output to speakers while headphones are plugged inAfter much searching through Apple documentation and scarce examples of what I wanted to do, I came up with the following code. A client wanted to play audio through the iPhone/iPad speakers while a microphone was plugged in. While this solution can't do both at the same time, it will let you switch back and forth between playing sounds through the speakers, then record through a microphone or a headset, without unplugging anything. It will also default to use the internal microphone and speakers if nothing is plugged in. Note that by calling the setup method, audio output will initially be forced through the speakers, rather than the headphones, if plugged in. Hopefully this code helps someone facing similar issues.
<br><br>
AudioRouter.h
<pre>
@interface AudioRouter : NSObject
+ (void) initAudioSessionRouting;
+ (void) switchToDefaultHardware;
+ (void) forceOutputToBuiltInSpeakers;
@end
</pre>
AudioRouter.m
<pre>
#import "AudioRouter.h"
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
@implementation AudioRouter
#define IS_DEBUGGING NO
#define IS_DEBUGGING_EXTRA_INFO NO
+ (void) initAudioSessionRouting {
// Called once to route all audio through speakers, even if something's plugged into the headphone jack
static BOOL audioSessionSetup = NO;
if (audioSessionSetup == NO) {
// set category to accept properties assigned below
NSError *sessionError = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error: &sessionError];
// Doubly force audio to come out of speaker
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, sizeof(audioRouteOverride), &audioRouteOverride);
// fix issue with audio interrupting video recording - allow audio to mix on top of other media
UInt32 doSetProperty = 1;
AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof(doSetProperty), &doSetProperty);
// set active
[[AVAudioSession sharedInstance] setDelegate:self];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
// add listener for audio input changes
AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, onAudioRouteChange, nil );
AudioSessionAddPropertyListener (kAudioSessionProperty_AudioInputAvailable, onAudioRouteChange, nil );
}
// Force audio to come out of speaker
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
// set flag
audioSessionSetup = YES;
}
+ (void) switchToDefaultHardware {
// Remove forcing to built-in speaker
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_None;
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, sizeof(audioRouteOverride), &audioRouteOverride);
}
+ (void) forceOutputToBuiltInSpeakers {
// Re-force audio to come out of speaker
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, sizeof(audioRouteOverride), &audioRouteOverride);
}
void onAudioRouteChange (void* clientData, AudioSessionPropertyID inID, UInt32 dataSize, const void* inData) {
if( IS_DEBUGGING == YES ) {
NSLog(@"==== Audio Harware Status ====");
NSLog(@"Current Input: %@", [AudioRouter getAudioSessionInput]);
NSLog(@"Current Output: %@", [AudioRouter getAudioSessionOutput]);
NSLog(@"Current hardware route: %@", [AudioRouter getAudioSessionRoute]);
NSLog(@"==============================");
}
if( IS_DEBUGGING_EXTRA_INFO == YES ) {
NSLog(@"==== Audio Harware Status (EXTENDED) ====");
CFDictionaryRef dict = (CFDictionaryRef)inData;
CFNumberRef reason = CFDictionaryGetValue(dict, kAudioSession_RouteChangeKey_Reason);
CFDictionaryRef oldRoute = CFDictionaryGetValue(dict, kAudioSession_AudioRouteChangeKey_PreviousRouteDescription);
CFDictionaryRef newRoute = CFDictionaryGetValue(dict, kAudioSession_AudioRouteChangeKey_CurrentRouteDescription);
NSLog(@"Audio old route: %@", oldRoute);
NSLog(@"Audio new route: %@", newRoute);
NSLog(@"=========================================");
}
}
+ (NSString*) getAudioSessionInput {
UInt32 routeSize;
AudioSessionGetPropertySize(kAudioSessionProperty_AudioRouteDescription, &routeSize);
CFDictionaryRef desc; // this is the dictionary to contain descriptions
// make the call to get the audio description and populate the desc dictionary
AudioSessionGetProperty (kAudioSessionProperty_AudioRouteDescription, &routeSize, &desc);
// the dictionary contains 2 keys, for input and output. Get output array
CFArrayRef outputs = CFDictionaryGetValue(desc, kAudioSession_AudioRouteKey_Inputs);
// the output array contains 1 element - a dictionary
CFDictionaryRef diction = CFArrayGetValueAtIndex(outputs, 0);
// get the output description from the dictionary
CFStringRef input = CFDictionaryGetValue(diction, kAudioSession_AudioRouteKey_Type);
return [NSString stringWithFormat:@"%@", input];
}
+ (NSString*) getAudioSessionOutput {
UInt32 routeSize;
AudioSessionGetPropertySize(kAudioSessionProperty_AudioRouteDescription, &routeSize);
CFDictionaryRef desc; // this is the dictionary to contain descriptions
// make the call to get the audio description and populate the desc dictionary
AudioSessionGetProperty (kAudioSessionProperty_AudioRouteDescription, &routeSize, &desc);
// the dictionary contains 2 keys, for input and output. Get output array
CFArrayRef outputs = CFDictionaryGetValue(desc, kAudioSession_AudioRouteKey_Outputs);
// the output array contains 1 element - a dictionary
CFDictionaryRef diction = CFArrayGetValueAtIndex(outputs, 0);
// get the output description from the dictionary
CFStringRef output = CFDictionaryGetValue(diction, kAudioSession_AudioRouteKey_Type);
return [NSString stringWithFormat:@"%@", output];
}
+ (NSString*) getAudioSessionRoute {
/*
returns the current session route:
* ReceiverAndMicrophone
* HeadsetInOut
* Headset
* HeadphonesAndMicrophone
* Headphone
* SpeakerAndMicrophone
* Speaker
* HeadsetBT
* LineInOut
* Lineout
* Default
*/
UInt32 rSize = sizeof (CFStringRef);
CFStringRef route;
AudioSessionGetProperty (kAudioSessionProperty_AudioRoute, &rSize, &route);
if (route == NULL) {
NSLog(@"Silent switch is currently on");
return @"None";
}
return [NSString stringWithFormat:@"%@", route];
}
@end
</pre>
justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com2tag:blogger.com,1999:blog-3911337215074644290.post-57421229997084371092013-07-07T19:18:00.000-06:002013-07-07T19:22:24.977-06:00Inspiration: Yoichiro KawaguchiIf you get into any of the many facets of creative coding or graphical programming, you inevitably run into the history of the <a href="http://en.wikipedia.org/wiki/Demoscene">demoscene</a>. I don't know too much about the specific history, but I enjoy watching old and new demos and learning more about the building blocks of modern generative graphics techniques. My good friend and veteran graphical programmer Kris just introduced me to one of the original masters and pioneers, <a href="http://www.yoichiro-kawaguchi.com/en">Yoichiro Kawaguchi</a>. It seems that he's gone into some really interesting territory with his work, including creation of toys and physical sculptures based on his algorithmic work. I've included some videos and visuals, as well as some links to show off some of what I found:
<br/><br/>
<i>283 Useful Ideas from Japan - 1990 - The Techno Deep</i>
<br/><br/>
<iframe width="560" height="315" src="//www.youtube.com/embed/c1ArwqWwHgo" frameborder="0" allowfullscreen></iframe>
<br/><br/>
Origin (1985)
<br/><br/>
<iframe width="420" height="315" src="//www.youtube.com/embed/a01jq6VcfwA" frameborder="0" allowfullscreen></iframe>
<br/><br/>
Embryo (1988)
<br/><br/>
<iframe width="420" height="315" src="//www.youtube.com/embed/HDZ6mNYX_-4" frameborder="0" allowfullscreen></iframe>
<br/><br/>
Mutation (1992)
<br/><br/>
<iframe width="420" height="315" src="//www.youtube.com/embed/QUaxM2eD5t0" frameborder="0" allowfullscreen></iframe>
<br/><br/>
Gigalopolis (1995)
<br/><br/>
<iframe width="420" height="315" src="//www.youtube.com/embed/nlCbw4WVynw" frameborder="0" allowfullscreen></iframe>
<br/><br/>
Cyloton (2002)
<br/><br/>
<iframe width="420" height="315" src="//www.youtube.com/embed/ChFAFUYpyf0" frameborder="0" allowfullscreen></iframe>
<br/><br/>
<i>Yoichiro Kawaguchi Exhibition at Yushima Seido (2009)</i>
<br/><br/>
<a href="http://www.flickr.com/photos/nobihaya/sets/72157613431829799/with/3260828552/"><img width="100%" border="0" src="http://farm4.staticflickr.com/3386/3260828986_ae0c3b49bf_z.jpg" /></a>
<br/><br/>
<i>Virtual creature simulations (2011)</i>
<br/><br/>
<iframe width="420" height="315" src="//www.youtube.com/embed/30CSbp3r1eM" frameborder="0" allowfullscreen></iframe>
<br/><br/>
<i>Gross Tendril (2012)</i>
<br/><br/>
<a href="http://tokyobling.wordpress.com/2013/05/03/yoichiro-kawaguchi-gross-tendril/"><img width="100%" border="0" src="http://tokyobling.files.wordpress.com/2013/05/kawaguchi_yoichiro_2182.jpg?w=720&h=479" /></a>
<br/><br/>
<br/><br/>
Links:
<ul>
<li><a href="http://www.bbc.co.uk/news/entertainment-arts-23028961"> Standing ovation for robot musicians (BBC)</a> (video)</li>
</ul>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-23002003626947657802013-05-27T21:43:00.000-06:002013-05-27T21:43:35.289-06:00JavaScript: Throttle requestAnimationFrame to maintain 30fpsOne problem with using requestAnimationFrame is that rendering will take place as quickly as the computer can process the per-frame calculations and screen redraw. If you only want to run at 30fps, your computer might be running a lot faster than you want. To work around this problem, simply check the elapsed time before running the next frame update. Check out the example:
<pre>
var frameLength = 33; // this is ~1/30th of a second, in milliseconds (1000/30)
var lastFrame = 0;
var render = function() {
if(Date.now() - lastFrame > frameLength) {
lastFrame = Date.now()
// run your 30fps code here...
}
requestAnimationFrame(render);
};
requestAnimationFrame(render);
</pre>
You'll notice that I'm using Date.now(), which requires a <a href="https://gist.github.com/eliperelman/1035932">polyfill</a> for old versions of IE. requestAnimationFrame also requires a <a href="https://gist.github.com/paulirish/1579671">polyfill</a> for some browsers. Another solution is to use time-based calculations, but that's not always easy to implement. justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-28408108452588692862013-05-25T08:05:00.000-06:002013-05-25T08:10:23.829-06:00Bookmarklet: Scrub through a Vine videoI was watching a friend's Vine video on the web, and I got the idea that it would be cool to control the playback of the video. I wrote this little bookmarklet to scrub through the video as you move your mouse over it.
Here's the original JavaScript:
<pre>
// grab video element and pause it
var vid = document.getElementById('post_html5_api');
vid.pause();
// get x offset of video
var vidX = 0;
var el = vid;
while (el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
vidX += el.offsetLeft;
el = el.offsetParent;
}
// scrub the video based on mouse x
var vidTime = vid.seekable.end(0);
vid.addEventListener('mousemove', function(e) {
var x = e.clientX - vidX;
var percent = x / vid.offsetWidth;
vid.currentTime = percent * vidTime;
}, false);
</pre>
And the bookmarklet (<a href="javascript:(function()%7Bvar%20vid=document.getElementById('post_html5_api');vid.pause();var%20vidX=0;var%20el=vid;while(el&&!isNaN(el.offsetLeft)&&!isNaN(el.offsetTop))%7BvidX+=el.offsetLeft;el=el.offsetParent;%7Dvar%20vidTime=vid.seekable.end(0);vid.addEventListener('mousemove',function(e)%7Bvar%20x=e.clientX-vidX;var%20percent=x/vid.offsetWidth;vid.currentTime=percent*vidTime;%7D,false)%7D)();">Vine Scrubber</a>):
<pre>
javascript:(function()%7Bvar%20vid=document.getElementById('post_html5_api');vid.pause();var%20vidX=0;var%20el=vid;while(el&&!isNaN(el.offsetLeft)&&!isNaN(el.offsetTop))%7BvidX+=el.offsetLeft;el=el.offsetParent;%7Dvar%20vidTime=vid.seekable.end(0);vid.addEventListener('mousemove',function(e)%7Bvar%20x=e.clientX-vidX;var%20percent=x/vid.offsetWidth;vid.currentTime=percent*vidTime;%7D,false)%7D)();
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-14637219842240003282013-04-28T17:27:00.000-06:002013-04-28T17:27:59.014-06:00Bookmarklet: Select & invite all friends on FacebookThis may be an evil thing, as I hate getting unwanted invites and spam on Facebook... But if you're throwing an event or have created a Facebook "Page", you might want to invite a bunch of people. You probably don't want to have to click each person's name/picture to add them to the invite, so I wrote a little bookmarklet to select them all at once. Simply scroll down to the bottom of your list of friends (it will load more in as you scroll). Once your (no-longer) friends have all loaded, click the bookmarklet to check them all.
Here's the original code:
<pre>var checks = document.getElementsByClassName('checkableListItem');
for(i=0; i<checks.length; i++){ $(checks[i]).click(); }
</pre>
And the same code, reformatted for a bookmarklet:
<pre>javascript:(function()%7Bvar checks%3Ddocument.getElementsByClassName(%27checkableListItem%27)%3Bfor(i%3D0%3Bi<checks.length%3Bi%2B%2B)%7B%24(checks%5Bi%5D).click()%3B%7D%7D)()%3B</pre>
I shall pay for this with spam karma. justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-45833212346523615542013-04-06T14:32:00.001-06:002013-04-08T07:01:27.519-06:00JavaScript: Use the goo.gl link shortener from your own siteHere's a quick, stripped-down version of a javascript implementation of the <a href="http://goo.gl/">goo.gl</a> link-shortener service. It asynchronously loads the Google client API, then uses another callback when the link shortener service is loaded. After the service loads, you can call <b>shortenUrl()</b> as many times as you'd like. For simplicity, I've only shortened one URL here. It doesn't appear that you need an API key to simply shorten URLs, but certain calls to this service would require one. Here's the basic version, which should work in modern browsers.
<pre>
var shortenUrl = function() {
var request = gapi.client.urlshortener.url.insert({
resource: {
longUrl: 'http://plasticsoundsupply.com'
}
});
request.execute(function(response) {
var shortUrl = response.id;
console.log('short url:', shortUrl);
});
};
var googleApiLoaded = function() {
// gapi.client.setApiKey("YOUR API KEY")
gapi.client.load("urlshortener", "v1", shortenUrl);
};
window.googleApiLoaded = googleApiLoaded;
$(document.body).append('<script src="https://apis.google.com/js/client.js?onload=googleApiLoaded"></script>');
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-44809672996735370522013-03-02T12:58:00.000-07:002013-03-02T13:01:26.252-07:00JavaScript: Antialias post-processing with THREE.js on a (non) retina screenWhen drawing a basic Mesh object in THREE.js, the edges can be particularly jagged if your browser doesn't properly support antialiasing in webGL (most don't seem to at the moment). In my current project this became a sticking point, and I set out to fix the aliased edges of my 3D models and Mesh objects.<br/><br/>
I found the FXAA post-processing shader effect in the THREE.js library, and it worked like a charm to smooth the rough edges. However, the THREE.EffectComposer utility doesn't automatically handle different pixel densities, and by default, the aliasing actually became twice as bad on the Retina screen of my Mac. After some fiddling, I found that you simply have to adjust the uniforms for the shader effect if it depends on knowing your screen size, as well as set the screen size for the EffectsComposer object.<br/><br/>
See below, where I detect the pixel density, and use that to multiply your screen dimensions in the shader and EffectComposer:
<pre>
var composer, dpr, effectFXAA, renderScene;
dpr = 1;
if (window.devicePixelRatio !== undefined) {
dpr = window.devicePixelRatio;
}
renderScene = new THREE.RenderPass(scene, camera);
effectFXAA = new THREE.ShaderPass(THREE.FXAAShader);
effectFXAA.uniforms['resolution'].value.set(1 / (window.innerWidth * dpr), 1 / (window.innerHeight * dpr));
effectFXAA.renderToScreen = true;
composer = new THREE.EffectComposer(renderer);
composer.setSize(window.innerWidth * dpr, window.innerHeight * dpr);
composer.addPass(renderScene);
composer.addPass(effectFXAA);
</pre>
You'll also probably want to update these settings if the window size changes, like so:
<pre>
$(window).on('resize', onWindowResize);
function onWindowResize(e) {
effectFXAA.uniforms['resolution'].value.set(1 / (window.innerWidth * dpr), 1 / (window.innerHeight * dpr));
composer.setSize(window.innerWidth * dpr, window.innerHeight * dpr);
}
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com3tag:blogger.com,1999:blog-3911337215074644290.post-63302041061737263572013-02-05T11:07:00.000-07:002013-05-06T16:11:14.369-06:00Base64 encode an image from the command line in OS XI frequently use base64 encoding to include small images inline in my CSS. This helps me avoid loading lots of small images or managing an image sprite. Luckily, there's a super easy native command line tool in Mac OS X to do this. Use it like so:
<pre>
openssl base64 -A -in your-image.png
</pre>
If your image file is valid, your Terminal will generate a base64 string. You can then drop this into your CSS as a background-image or as the src of an <strong>img</strong> tag. You simply need to prepend the base64 string with the following header string:
<pre>
data:image/png;base64,
</pre>
In the 2 cases, your code would look something like this:
<pre>
/* CSS */
#container {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAbCAYAAABr/T8RAAAB8qpOejw03OsRMxMR8RDujjC14YwEWEg/bF/6glXHxYm2JTCa4xRxoT/gW5/67s0Hu88AAAAABJRU5ErkJggg==")
}
</pre>
<pre>
<!-- HTML -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAbCAYAAABr/T8RAAAB8qpOejw03OsRMxMR8RDujjC14YwEWEg/bF/6glXHxYm2JTCa4xRxoT/gW5/67s0Hu88AAAAABJRU5ErkJggg==" />
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-16414362069230827932012-10-24T13:31:00.001-06:002012-10-26T18:37:24.724-06:00JavaScript: Letterbox, pillowbox or crop an image to any container sizeVery often, I've run across the situation of having images of an unspecified size that need to fit into a container of an unspecified size, either letterboxed, or cropped to fill the container. This is easily accomplished by scaling the image, positioning it within the container, and masking the container's contents. With the following methods, it's easy to crop or letterbox/pillowbox an image for your particular use case.<br/><br/>Here's a method that will give you the size and position of your image, based on its relation to the container's size. The method receives the container's dimensions, the image's original size, and a boolean to specify cropping or letterboxing. The method returns an array with the coordinates and dimensions to reposition and resize your image. If you don't know the size of your images beforehand, you can use <a href="http://uihacker.blogspot.com/2012/03/javascript-get-original-size-of-image.html">this method</a> to find out before using the following code.
<pre>
var ImageUtil = ImageUtil || {};
ImageUtil.getOffsetAndSizeToCrop = function( containerW, containerH, imageW, imageH, cropFill ) {
var ratioW = containerW / imageW;
var ratioH = containerH / imageH;
var shorterRatio = ratioW > ratioH ? ratioH : ratioW;
var longerRatio = ratioW > ratioH ? ratioW : ratioH;
var resizedW = cropFill ? Math.ceil(imageW * longerRatio) : Math.ceil(imageW * shorterRatio);
var resizedH = cropFill ? Math.ceil(imageH * longerRatio) : Math.ceil(imageH * shorterRatio);
var offsetX = Math.ceil((containerW - resizedW) * 0.5);
var offsetY = Math.ceil((containerH - resizedH) * 0.5);
return [offsetX, offsetY, resizedW, resizedH];
};
</pre>
To wrap up the cropping functionality, you can use the following method to apply the css styles to your image element based on its size, the container's size, and the type of image resizing you'd like. I've included special cases to anchor the image to top or bottom of the container rather than vertically centering it, in case the images' content requires that type of positioning. Note that the method requires a raw html element reference for the image and container - this doesn't require jQuery or any such library.
<pre>
ImageUtil.CROP = 'CROP';
ImageUtil.CROP_TOP = 'CROP_TOP';
ImageUtil.CROP_BOTTOM = 'CROP_BOTTOM';
ImageUtil.LETTERBOX = 'LETTERBOX';
ImageUtil.cropImage = function( containerEl, containerW, containerH, imageEl, imageW, imageH, cropType ) {
var cropFill = ( cropType == ImageUtil.CROP || cropType == ImageUtil.CROP_TOP || cropType == ImageUtil.CROP_BOTTOM );
var offsetAndSize = ImageUtil.getOffsetAndSizeToCrop(containerW, containerH, imageW, imageH, cropFill);
// set outer container size
containerEl.style.width = containerW+'px';
containerEl.style.height = containerH+'px';
// resize image
imageEl.width = offsetAndSize[2];
imageEl.height = offsetAndSize[3];
imageEl.style.width = offsetAndSize[2]+'px';
imageEl.style.height = offsetAndSize[3]+'px';
// position image
imageEl.style.left = offsetAndSize[0]+'px';
imageEl.style.top = offsetAndSize[1]+'px';
// special y-positioning
if( cropType == utensils.ImageUtil.CROP_TOP ) {
imageEl.style.top = '0px';
imageEl.style.bottom = '';
} else if( cropType == utensils.ImageUtil.CROP_BOTTOM ) {
imageEl.style.top = '';
imageEl.style.bottom = '0px';
}
};
</pre>
The only default styles you'd need on your container and image are as follows:
<pre>
.cropped-image {
position: absolute;
}
.container {
position: relative;
overflow: hidden;
}
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-13729936317052828052012-09-11T16:10:00.000-06:002012-09-11T16:13:09.725-06:00Obj-C: Center a view horizontally with autoresizingMask propertiesCocoa's UIView layout system gives the developer some nice tools to automatically resize and reposition views inside of each other. This is usually done in the nib/xib/storyboard editor with the Origin/Autoresizing View property inspectors, and honestly it's never made a ton of sense to me. I'm only an occasional obj-c dev, so bear with me. I wanted to horizontally center and fill some UIView elements to fit the width of different devices and orientations, and I knew there should be a simple way to accomplish this. I came up with several methods to help apply these settings to any number of outer containers that should fill the width of the parent view, and inner containers that should be centered within:
<pre>
- (void)setViewCentered:(UIView*)view
{
view.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin);
}
- (void)setContainerToParentWidth:(UIView*)view
{
view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
}
- (int)getHorizCenterFromView:(UIView*)view
{
return (self.view.bounds.size.width - view.frame.size.width)/2;
}
- (int)getHorizCenterFromInt:(int)width
{
return (self.view.bounds.size.width - width)/2;
}
</pre>
The key to centering a fixed-width view - this was the confusing part for me - is setting the x-position of the <strong>frame</strong> to 1/2 of the width of the parent container. The associated example call to center a view is here. From my researching, it seems that the <strong>autoresizingMask</strong> property should be changed after <strong>addSubview</strong>
<pre>
int viewW = 320;
_controlsContainer = [[UIView alloc] initWithFrame:CGRectMake([self getHorizCenterFromInt:viewW], 0, viewW, controlsH)];
[self.view addSubview:_controlsContainer];
[self setViewCentered:_controlsContainer];
</pre>
Note that <strong>self.view</strong> is the outer container that we're centering inside of. Then if you simply want to fill a container to the width of the parent, use something like this:
<pre>
_header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
[self.view addSubview:_header];
[self setContainerToParentWidth:_header];
</pre>
Happy centering without having to change a UIView's <strong>frame</strong>!justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-32428124238998640142012-07-09T17:22:00.002-06:002012-07-31T12:02:53.548-06:00HTML5: Mobile web dev notes for games & fancy interfacesNote: Use vendor prefixes for CSS3 styles mentioned here.
<ul>
<li>Bind to <strong>touchend</strong> events rather than <strong>click</strong> events for far more responsive click/tap handling. This is one of the most elementary steps to make a mobile site feel more app-like. <a href="https://github.com/h5bp/mobile-boilerplate/blob/master/js/helper.js">Mobile boilerplate</a> has a nice way to do this (see the fastButton methods).</li>
<li>Lock your window in place by canceling a <strong>touchmove</strong> event on <strong>document</strong>:<pre>
var lockTouchScreen = function( locks ) {
if( locks == true ) {
document.ontouchmove = function( event ) {
event.preventDefault();
};
} else {
document.ontouchmove = null;
}
};
</pre></li>
<li>Hide the browser address bar to gain more screen real estate. There's another nice method for this in the <a href="https://github.com/h5bp/mobile-boilerplate/blob/master/js/helper.js">Mobile boilerplate</a> project (see hideUrlBar). An important note about this working properly: your content has to be tall enough to convince the browser it needs to hide the address bar. This may require manually setting the height property of an outer container to <strong>$(window).height() + 50</strong> before calling hideUrlBar().</li>
<li>Use <strong>transform: translate3d(0,0,0)</strong> to hardware accelerate the movement and CSS animation of individual HTML elements. This is true for all version of iOS, and Android 4.0 and newer. However, there are some potential side-effects described below.</li>
<li>Using the above hardware acceleration trick will accelerate any CSS animations that are added to the same element, such as opacity, background-color, width, etc.</li>
<li>Sometimes z-index is ignored if you've used <strong>transform: translate3d(0,0,0)</strong> and placed a non-accelerated button above an accelerated element. The accelerated element can block clicks or touch events. (iOS)</li>
<li>Adding this acceleration to sub-elements inside an accelerated container can further improve performance. For example, a series of img elements inside a larger element that's being positioned with translate3d (iOS)</li>
<li>Swapping a background image with a class toggle on an element that's been hardware-accelerated can lead to the element disappearing. (Intermittent on iOS)</li>
<li>Android ignores z-position in translate3d positioning as of this posting</li>
<li>Android ignores background-size in 2.x versions, and possibly later versions, though Android 4.x seems to fix the problem.</li>
<li>iOS can get really chunky when it's loading images. For less chunky loading, use containers with smaller background images, and use webkit scaling to size them up to the right size.</li>
<li>Make sure you're loading @2x images for retina screens, or larger screens if you need to fill up a larger area. Also make sure you're not double-loading by using max <strong>and</strong> min pixel density media queries:<br/>(-webkit-min-device-pixel-ratio: 1.5) <br/>(-webkit-max-device-pixel-ratio: 1.5)</li>
<li>It really can't hurt to turn off <strong>backface-visibility: none</strong> - this is a common optimization in 3D programming, though I would expect browsers to handle it by default?</li>
<li>Holding a touch down on some versions of Android will reduce the image quality of images on the page, thus increasing framerate significantly for elements that are being animated or updated each frame. This fact could be exploited for gaming purposes...</li>
<li>Using scale and translate3d at the same time on an element will break on Android 2.x and maybe others. For better cross-platform positioning/sclaing, use the technique described in my <a href="http://uihacker.blogspot.com/2012/06/android-fix-android-2x-css-transform.html">previous post</a>.</li><li>This Android transform bug is not only on the same element - if a container that's been scaled has a child element with a <strong>transform</strong>, animating <strong>transition</strong> or <strong>backface-visibility</strong> defined, the outer container will lose its scale. This is a MAJOR bug in Android 2.2 and 2.3, and there are probably other properties that will break <strong>transform: scale()</strong>.</li>
<li>Detect pinch gesture capabilities with <strong>isEventSupported('gesturestart')</strong>, using the <a href="http://perfectionkills.com/detecting-event-support-without-browser-sniffing/">event support detection method by Kangax</a>. Then use my <a href="http://uihacker.blogspot.com/2011/04/javascript-handle-touch-gestures-for.html">GestureCallback class</a> to perform the pinch detection, or fall back to another input style.</li>
<li>The default iOS menu that shows when an iOS user taps & holds an <strong>img</strong> element can be blocked if the <strong>img</strong> element is within a container that has a <strong>transform: translate3d(0,0,0);</strong> applied to it. Though, much like the button-blocking note above, by adding <strong>transform: translate3d(0,0,0);</strong> to the <strong>img</strong>, the issue is solved.</li>
<li>Likewise on Android, this same menu that lets you save or copy an <strong>img</strong> can be blocked when using the <a href="http://code.google.com/p/android/issues/detail?id=5491"><strong>touchstart</strong> event-canceling trick</a> that's needed to provide a draggable interface. I'm not sure what a good fix would be for this if you want to use the image menu inside a draggable element.</li>
<li>When detecting accelerometer support, it's not enough to check existence of the <strong>devicemotion</strong> event. You have to read the properties of the incoming event and check for real numbers. Something like: <strong>if( event.acceleration != undefined && event.acceleration.x != undefined ) var hasAccel = true;</strong></li><li>iOS caches the previous page you've visited, so if you press the browser back button, you can end up in a broken state if your page requires a re-initialization that would've happened on <strong>$(document).ready()</strong>. In my case, I was using <strong>document.location = '/new-location';</strong>. To force a reload if the user comes back via the browser back button, I used the following code:<pre>
document.location = '/new-location';
// reload page if we end up back at a cached page
setTimeout(function(){
window.location.reload();
},3000);</pre>
The browser will execute the timeout the user comes back, and problem solved.</li>
</ul>
More to come...justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-63583517267645568132012-06-19T22:41:00.000-06:002012-06-20T19:12:41.241-06:00Android: fix the Android 2.x CSS transform bug with translate3d() and scale()There's a serious bug with the use of the CSS3 <strong>transform</strong> property in Android's webkit browser, specifically in Android versions 2.2 and 2.3. The problem is that the <strong>scale()</strong> property is discarded in the presence of <strong>translate3d()</strong> (and other applicable <strong>transform</strong> properties). I can confirm it doesn't happen in 2.1, and I'm not seeing it consistently in all versions/devices with 2.3.x. The bug is officially documented <a href="http://code.google.com/p/android/issues/detail?id=12451">here</a>.
There is a workaround, unfortunate as the bug is, which is to separately apply the <strong>translate3d()</strong> property to an outer element, and <strong>scale()</strong> to an inner element, like so:<br/><br/>
Markup:
<pre><div class="outer">
<div class="inner"></div>
</div>
</pre>
CSS:
<pre>.outer {
transform: translate3d(100px, 100px, 0px)
-webkit-transform: translate3d(100px, 100px, 0px)
/* add other vendor prefixes so all browsers handle your hack */
}
.inner {
transform: scale(5)
/* add vendor-prefixed property again */
}
</pre>
This doesn't work with <strong>scale()</strong> on the outer element, and <strong>translate3d()</strong> on the inner element, for what it's worth.justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-77484260814123928342012-05-27T20:16:00.000-06:002012-05-27T20:16:26.330-06:00CSS: Rotation animation with CSS3Sometimes you want to continuously spin something on your web page. In my case I was spinning a .png image for a smooth loading indicator. The following CSS will spin any element:<pre>
/* rotation animation */
@-webkit-keyframes rotate {
from { -webkit-transform:rotate(0deg); }
to { -webkit-transform:rotate(360deg); }
}
@-moz-keyframes rotate {
from { -moz-transform:rotate(0deg); }
to { -moz-transform:rotate(360deg); }
}
@-ms-keyframes rotate {
from { -ms-transform:rotate(0deg); }
to { -ms-transform:rotate(360deg); }
}
@-o-keyframes rotate {
from { -o-transform:rotate(0deg); }
to { -o-transform:rotate(360deg); }
}
.rotating {
-webkit-transform-origin: 50% 50%;
-webkit-animation-name: rotate;
-webkit-animation-duration: 1.5s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
-moz-transform-origin: 50% 50%;
-moz-animation-name: rotate;
-moz-animation-duration: 1.5s;
-moz-animation-iteration-count: infinite;
-moz-animation-timing-function: linear;
-ms-transform-origin: 50% 50%;
-ms-animation-name: rotate;
-ms-animation-duration: 1.5s;
-ms-animation-iteration-count: infinite;
-ms-animation-timing-function: linear;
-o-transform-origin: 50% 50%;
-o-animation-name: rotate;
-o-animation-duration: 1.5s;
-o-animation-iteration-count: infinite;
-o-animation-timing-function: linear;
}
</pre>
Just add/remove the <strong>.rotating</strong> class to an element to start/stop the animation.justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-13852032670362999362012-04-23T12:09:00.000-06:002012-04-23T12:09:08.462-06:00Processing: Flip a PImage horizontallyI needed a mirror image of a <strong>PImage</strong> in my Processing project, so I came up with this little snippet:
<pre>
public PImage getReversePImage( PImage image ) {
PImage reverse = new PImage( image.width, image.height );
for( int i=0; i < image.width; i++ ){
for(int j=0; j < image.height; j++){
reverse.set( image.width - 1 - i, j, image.get(i, j) );
}
}
return reverse;
}
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-36762374747057764262012-04-12T17:32:00.003-06:002012-04-12T17:35:48.294-06:00Javascript: Array.splice() is "broken" in IE 8 and belowIt's not exactly broken, because all of the <strong><a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice">Array.splice()</a></strong> documentation that I've found says that the first 2 parameters - (<strong>start index</strong> and <strong>delete count</strong>) - are required. However, it seems that most modern browsers allow the 2nd parameter to be optional, and IE9 has made this the case as well. By leaving the 2nd parameter off, it assumes that you passed in the length of the array and clears everything past the <strong>start index</strong>. I found the issue when using <strong>array.splice(0)</strong> to empty an array, but it wasn't getting emptied in IE7 and IE8. My confusion came from my past life as an ActionScript developer, where the 2nd <strong>delete count</strong> parameter was optional. From now on, I'll be using at least the 2 required parameters for legacy IE support.justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-66545184520021818162012-03-23T15:54:00.000-06:002012-03-23T23:50:25.471-06:00Javascript: Remove duplicate values from an arrayI did a Google search for different methods of removing duplicate values from an array, and I found a bunch of crappy algorithms that all returned a new array, rather than paring down the original array. So I wrote this, which wouldn't work on identical complex objects, but works great for primitive data types and objects stored by reference.
<pre>
function removeDuplicates(arr) {
arr.sort();
var i = arr.length - 1;
while (i > 0) {
if (arr[i] === arr[i - 1]) arr.splice(i, 1);
i--;
}
}
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-28775079724390187682012-03-22T18:01:00.003-06:002012-10-24T11:55:46.706-06:00Javascript: Get the original size of an imageSometimes you need to know the original dimensions of an image that you've loaded via javascript, or that's already loaded in the DOM. Here's a little class that will take the location of an image, and a callback function that receives the original width and height:
<pre>
var getImageSizeWithCallback = function( src, callback ) {
var image = new Image();
image.onload = function () {
if( callback ) callback( image.width, image.height );
image.onload = image.onerror = null;
};
image.onerror = function () {
if( callback ) callback( -1, -1 );
image.onload = image.onerror = null;
};
// load it
image.src = src;
};
</pre>
Usage:
<pre>
getImageSizeWithCallback( 'images/path/to/img.jpg', function( w, h ) {
console.log( 'image size:', w, h );
});
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-35969301654012686202012-03-06T10:44:00.000-07:002012-03-06T11:02:29.137-07:00Bookmarklet: Scrape a web page for YouTube videos and generate a playlistI wrote this little script to capture about 50 YouTube videos from a music blog I was browsing. I figured it would be useful for others, so I turned it into a bookmarklet:
<pre>
// get html source<br/>var src = document.getElementsByTagName('html')[0].innerHTML;<br/>// handle old youtube vids, and https<br/>var vids = src.replace(/\"/g).replace(/http:\/\/www.youtube.com\/v\//gi,'http://www.youtube.com/embed/').replace(/https/gi,'http');<br/>// parse the source for youtube embeds<br/>var vidArr = vids.split('http://www.youtube.com/embed/')<br/>var links = ''; <br/>// build string for playlist<br/>var playlist = 'http://www.ytplaylist.com/?pl='; <br/>// build links<br/>if(vidArr.length > 1) {<br/> // keep track of IDs so we don't get duplicates<br/> var videos = [];<br/> for(var i=1; i < vidArr.length; i++) { <br/> // grab 11-character YouTube ID<br/> var ytID = vidArr[i].substr(0,11);<br/> if(videos.indexOf(ytID) == -1) {<br/> // add link to video, and to playlist link<br/> var link = 'http://www.youtube.com/watch?v='+ytID;<br/> links += '<a target="_blank" href="'+link+'">'+link+'</a><br/>';<br/> playlist += ytID+';';<br/> videos.push(ytID);<br/> }<br/> }<br/> playlist = playlist.substr(0,playlist.length-1);<br/> // draw it to the page<br/> var container = document.createElement('div');<br/> container.innerHTML = 'Videos Found:<br/><br/>'+links+'<br/><br/>'+'<a target="_blank" href="'+playlist+'">View as Playlist</a><br/>';<br/> container.style.position='fixed';<br/> container.style.background='#fff';<br/> container.style.border='5px solid black';<br/> container.style.padding='10px';<br/> container.style.top='0';<br/> container.style.right='0';<br/> container.style.zIndex='999';<br/> document.body.appendChild(container);<br/>} else {<br/> alert('No YouTube videos found');<br/>}
</pre>
And here's the bookmarklet link text:
<pre>
javascript:(function(){var%20src%20%3D%20document.getElementsByTagName('html')%5B0%5D.innerHTML%3B%0Avar%20vids%20%3D%20src.replace(%2F%5C%22%2Fg).replace(%2Fhttp%3A%5C%2F%5C%2Fwww.youtube.com%5C%2Fv%5C%2F%2Fgi%2C'http%3A%2F%2Fwww.youtube.com%2Fembed%2F').replace(%2Fhttps%2Fgi%2C'http')%3B%0Avar%20vidArr%20%3D%20vids.split('http%3A%2F%2Fwww.youtube.com%2Fembed%2F')%0Avar%20links%20%3D%20''%3B%20%0Avar%20playlist%20%3D%20'http%3A%2F%2Fwww.ytplaylist.com%2F%3Fpl%3D'%3B%20%0A%2F%2F%20build%20links%0Aif(vidArr.length%20%3E%201)%20%7B%0A%20%20var%20videos%20%3D%20%5B%5D%3B%0A%20%20for(var%20i%3D1%3B%20i%20%3C%20vidArr.length%3B%20i%2B%2B)%20%7B%20%0A%20%20%20%20var%20ytID%20%3D%20vidArr%5Bi%5D.substr(0%2C11)%3B%0A%20%20%20%20if(videos.indexOf(ytID)%20%3D%3D%20-1)%20%7B%0A%20%20%20%20%20%20var%20link%20%3D%20'http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D'%2BytID%3B%0A%20%20%20%20%20%20links%20%2B%3D%20'%3Ca%20target%3D%22_blank%22%20href%3D%22'%2Blink%2B'%22%3E'%2Blink%2B'%3C%2Fa%3E%3Cbr%2F%3E'%3B%0A%20%20%20%20%20%20playlist%20%2B%3D%20ytID%2B'%3B'%3B%0A%20%20%20%20%20%20videos.push(ytID)%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20playlist%20%3D%20playlist.substr(0%2Cplaylist.length-1)%3B%0A%20%20var%20container%20%3D%20document.createElement('div')%3B%0A%20%20container.innerHTML%20%3D%20'Videos%20Found%3A%3Cbr%2F%3E%3Cbr%2F%3E'%2Blinks%2B'%3Cbr%2F%3E%3Cbr%2F%3E'%2B'%3Ca%20target%3D%22_blank%22%20href%3D%22'%2Bplaylist%2B'%22%3EView%20as%20Playlist%3C%2Fa%3E%3Cbr%2F%3E'%3B%0A%20%20container.style.position%3D'fixed'%3B%0A%20%20container.style.background%3D'%23fff'%3B%0A%20%20container.style.border%3D'5px%20solid%20black'%3B%0A%20%20container.style.padding%3D'10px'%3B%0A%20%20container.style.top%3D'0'%3B%0A%20%20container.style.right%3D'0'%3B%0A%20%20container.style.zIndex%3D'999'%3B%0A%20%20document.body.appendChild(container)%3B%0A%7D%20else%20%7B%0A%20%20alert('No%20YouTube%20videos%20found')%3B%0A%7D}());
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-57265658009508285792012-02-29T23:00:00.001-07:002012-02-29T23:00:13.063-07:00iPad bug: using CSS transitions on :hover prevents child element from showingWhile building a CSS-powered drop-down navigation, I found a little iOS bug that prevented my main nav elements' sub-menus from showing on <strong>:hover</strong>. Consider the following markup and css, where a rollover on the outer <li> will reveal the inner <ul>.
<br />
<pre>
<header class="admin_nav">
<ul>
<li>
<a href="#" title="Users">
<span class="label">Users</span>
</a>
<ul>
<li>
<a href="#" title="Map">Map</a>
</li>
</ul>
</li>
</ul>
</header>
</pre>
<pre>
header.admin_nav ul li ul {
visibility:hidden;
opacity:0;
-webkit-transition: opacity 0.35s linear;
}
header.admin_nav ul li:hover ul {
visibility:visible;
opacity:1;
}
</pre>
On desktop browsers, this is a nice way to fade in a sub-menu on <strong>:hover</strong> of the main menu item. But, having the <strong>-webkit-transition</strong> definition breaks the iPad's handling of this nicely-animated menu. Get rid of the transition, and it works just fine, albeit not quite as slickly.justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-36108279438768685202012-01-05T21:02:00.000-07:002012-01-05T21:03:01.387-07:00Coffeescript: HTML5 <input> placeholder attribute fallbackYou want to use the new HTML5 <b>placeholder</b> attribute on <b>input</b> fields, but you also want to support older browsers. Here's a little Coffeescript class that takes an HTML element in the constructor, and uses jQuery to bind the <b>focus</b> and <b>blur</b> events to swap out text like a modern browser would without the script.
<pre>
# ## HTML5 placeholder feature fallback class
class PlaceholderFallback
constructor: (el) ->
@el = $(el)
@initialize()
# HTML5 <input> placeholder feature detection
browserHasPlaceholder: =>
"placeholder" of document.createElement("input")
# Reads the placeholder attribute and uses it in a javascript fallback, if needed
initialize: =>
if @browserHasPlaceholder() == false
placeholderText = @el.attr 'placeholder'
@el.removeAttr 'placeholder'
@el.val(placeholderText)
@el.focus (e) ->
if this.value == placeholderText
this.value = ''
@el.blur (e) ->
if this.value == ''
this.value = placeholderText
else
@el = null
# Usage:
placeholderFallback = new PlaceholderFallback( element )
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com0tag:blogger.com,1999:blog-3911337215074644290.post-3346991094181180812011-12-19T20:24:00.001-07:002011-12-19T20:32:24.140-07:00CSS: Disable text selection highlightingWhen working with touch & mouse events to drag html elements, I've occasionally resorted to disabling clicking/touching on child elements by doing something like this:
<pre>
elem.onmousedown = function(e){ return false; };
elem.onselectstart = function(){ return false; };
</pre>
But sometimes you want to be able to interact with those child elements... I found some nice CSS <a href="http://stackoverflow.com/questions/826782/css-rule-to-disable-text-selection-highlighting">over here</a> that prevents text & element highlighting when you're dragging with a mousemove or a touchmove event:
<pre>
p {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}
</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com1tag:blogger.com,1999:blog-3911337215074644290.post-853250376251801602011-12-11T22:36:00.001-07:002011-12-11T22:46:20.908-07:00Coffeescript: MapsLoader class for asynchronous loading of Google Maps APII'm using CoffeeScript for my current project, and we needed a way to load the Google Maps API when a user hits a particular view. This static class is auto-initialized, and all you need to call is: <b>MapsLoader.load(callbackFunction,true)</b>. If the API has already loaded, it will invoke your callback immediately. Make sure to pass the appropriate boolean if the user is on a mobile device (true), or a desktop browser (false).
<pre>class MapsLoader
constructor: ->
load: (successCallback,isMobileDevice) ->
@isMobileDevice = isMobileDevice
@successCallback = successCallback
if @hasLoaded != true
@loadGoogle()
else
@mapsLoaded()
loadGoogle: =>
# reference google loader callback to local method - clean up after callback
window.loadMaps = @loadMaps
apiKey = "-----your-api-key-here-----"
script = document.createElement("script")
script.src = "https://www.google.com/jsapi?key=#{apiKey}&callback=loadMaps"
script.type = "text/javascript"
document.getElementsByTagName("head")[0].appendChild(script)
loadMaps: =>
otherParams = if @isMobileDevice then "sensor=true" else "sensor=false"
google.load("maps", "3", {other_params: otherParams, "callback" : @mapsLoaded});
mapsLoaded: =>
@hasLoaded = true
window.loadMaps = null
if @successCallback
@successCallback()
@successCallback = null
@MapsLoader = new MapsLoader()</pre>justinhttp://www.blogger.com/profile/06696548898804101545noreply@blogger.com2