Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

October 26, 2014

JavaScript: Automatically scroll to the end of an infinitely-scrolling page

Simply paste this code into your browser console:
var scrollInterval = setInterval(function(){window.scrollTo(0,-999999999999)},500)
When it's done, clear out the auto-scroll by pasting this code into your console:
clearInterval(scrollInterval)
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:
window.addEventListener('beforeunload',function(e){e.returnValue = 'sure?';return 'sure?'});

May 8, 2014

Return to the beginning of an array with a ternary operator

Often 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!
var myArray = ['one', 'two', 'three', 'four'];
var curIndex = 0;

setInterval(function(){
    console.log(myArray[curIndex]);
    curIndex = (curIndex < myArray.length - 1) ? curIndex + 1 : 0;
}, 500);

May 27, 2013

JavaScript: Throttle requestAnimationFrame to maintain 30fps

One 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:
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);
You'll notice that I'm using Date.now(), which requires a polyfill for old versions of IE. requestAnimationFrame also requires a polyfill for some browsers. Another solution is to use time-based calculations, but that's not always easy to implement.

May 25, 2013

Bookmarklet: Scrub through a Vine video

I 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:
// 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);
And the bookmarklet (Vine Scrubber):
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)();

April 28, 2013

Bookmarklet: Select & invite all friends on Facebook

This 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:
var checks = document.getElementsByClassName('checkableListItem');
for(i=0; i<checks.length; i++){ $(checks[i]).click(); }
And the same code, reformatted for a bookmarklet:
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
I shall pay for this with spam karma.

April 6, 2013

JavaScript: Use the goo.gl link shortener from your own site

Here's a quick, stripped-down version of a javascript implementation of the goo.gl 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 shortenUrl() 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.
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>');

March 2, 2013

JavaScript: Antialias post-processing with THREE.js on a (non) retina screen

When 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.

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.

See below, where I detect the pixel density, and use that to multiply your screen dimensions in the shader and EffectComposer:
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);
You'll also probably want to update these settings if the window size changes, like so:
$(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);
}

July 9, 2012

HTML5: Mobile web dev notes for games & fancy interfaces

Note: Use vendor prefixes for CSS3 styles mentioned here.
  • Bind to touchend events rather than click 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. Mobile boilerplate has a nice way to do this (see the fastButton methods).
  • Lock your window in place by canceling a touchmove event on document:
    var lockTouchScreen = function( locks ) {
      if( locks == true ) {
        document.ontouchmove = function( event ) {
          event.preventDefault();
        };
      } else {
        document.ontouchmove = null;
      }
    };
    
  • Hide the browser address bar to gain more screen real estate. There's another nice method for this in the Mobile boilerplate 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 $(window).height() + 50 before calling hideUrlBar().
  • Use transform: translate3d(0,0,0) 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.
  • 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.
  • Sometimes z-index is ignored if you've used transform: translate3d(0,0,0) and placed a non-accelerated button above an accelerated element. The accelerated element can block clicks or touch events. (iOS)
  • 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)
  • 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)
  • Android ignores z-position in translate3d positioning as of this posting
  • Android ignores background-size in 2.x versions, and possibly later versions, though Android 4.x seems to fix the problem.
  • 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.
  • 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 and min pixel density media queries:
    (-webkit-min-device-pixel-ratio: 1.5)
    (-webkit-max-device-pixel-ratio: 1.5)
  • It really can't hurt to turn off backface-visibility: none - this is a common optimization in 3D programming, though I would expect browsers to handle it by default?
  • 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...
  • 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 previous post.
  • This Android transform bug is not only on the same element - if a container that's been scaled has a child element with a transform, animating transition or backface-visibility 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 transform: scale().
  • Detect pinch gesture capabilities with isEventSupported('gesturestart'), using the event support detection method by Kangax. Then use my GestureCallback class to perform the pinch detection, or fall back to another input style.
  • The default iOS menu that shows when an iOS user taps & holds an img element can be blocked if the img element is within a container that has a transform: translate3d(0,0,0); applied to it. Though, much like the button-blocking note above, by adding transform: translate3d(0,0,0); to the img, the issue is solved.
  • Likewise on Android, this same menu that lets you save or copy an img can be blocked when using the touchstart event-canceling trick 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.
  • When detecting accelerometer support, it's not enough to check existence of the devicemotion event. You have to read the properties of the incoming event and check for real numbers. Something like: if( event.acceleration != undefined && event.acceleration.x != undefined ) var hasAccel = true;
  • 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 $(document).ready(). In my case, I was using document.location = '/new-location';. To force a reload if the user comes back via the browser back button, I used the following code:
    document.location = '/new-location';
    // reload page if we end up back at a cached page
    setTimeout(function(){
        window.location.reload();
    },3000);
    The browser will execute the timeout the user comes back, and problem solved.
More to come...

April 12, 2012

Javascript: Array.splice() is "broken" in IE 8 and below

It's not exactly broken, because all of the Array.splice() documentation that I've found says that the first 2 parameters - (start index and delete count) - 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 start index. I found the issue when using array.splice(0) 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 delete count parameter was optional. From now on, I'll be using at least the 2 required parameters for legacy IE support.

March 23, 2012

Javascript: Remove duplicate values from an array

I 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.
function removeDuplicates(arr) {
  arr.sort();
  var i = arr.length - 1;
  while (i > 0) {
    if (arr[i] === arr[i - 1]) arr.splice(i, 1);
    i--;
  }
}

March 22, 2012

Javascript: Get the original size of an image

Sometimes 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:
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;
};

Usage:
getImageSizeWithCallback( 'images/path/to/img.jpg', function( w, h ) {
  console.log( 'image size:', w, h );
});

March 6, 2012

Bookmarklet: Scrape a web page for YouTube videos and generate a playlist

I 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:
// get html source
var src = document.getElementsByTagName('html')[0].innerHTML;
// handle old youtube vids, and https
var vids = src.replace(/\"/g).replace(/http:\/\/www.youtube.com\/v\//gi,'http://www.youtube.com/embed/').replace(/https/gi,'http');
// parse the source for youtube embeds
var vidArr = vids.split('http://www.youtube.com/embed/')
var links = '';
// build string for playlist
var playlist = 'http://www.ytplaylist.com/?pl=';
// build links
if(vidArr.length > 1) {
// keep track of IDs so we don't get duplicates
var videos = [];
for(var i=1; i < vidArr.length; i++) {
// grab 11-character YouTube ID
var ytID = vidArr[i].substr(0,11);
if(videos.indexOf(ytID) == -1) {
// add link to video, and to playlist link
var link = 'http://www.youtube.com/watch?v='+ytID;
links += '<a target="_blank" href="'+link+'">'+link+'</a><br/>';
playlist += ytID+';';
videos.push(ytID);
}
}
playlist = playlist.substr(0,playlist.length-1);
// draw it to the page
var container = document.createElement('div');
container.innerHTML = 'Videos Found:<br/><br/>'+links+'<br/><br/>'+'<a target="_blank" href="'+playlist+'">View as Playlist</a><br/>';
container.style.position='fixed';
container.style.background='#fff';
container.style.border='5px solid black';
container.style.padding='10px';
container.style.top='0';
container.style.right='0';
container.style.zIndex='999';
document.body.appendChild(container);
} else {
alert('No YouTube videos found');
}
And here's the bookmarklet link text:
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}());

January 5, 2012

Coffeescript: HTML5 <input> placeholder attribute fallback

You want to use the new HTML5 placeholder attribute on input 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 focus and blur events to swap out text like a modern browser would without the script.
# ## 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 )

December 11, 2011

Coffeescript: MapsLoader class for asynchronous loading of Google Maps API

I'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: MapsLoader.load(callbackFunction,true). 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).
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()

October 10, 2011

Javascript: Hide the iOS soft keyboard

I was having some trouble getting my text input field to relieve itself of focus on the iPhone, and after a little searching, I came up with a couple options. It's pretty self-explanatory. The 2nd line will de-focus all input fields, and it relies on jQuery. I found that calling blur() on the single focused textfield didn't always work. Either one of these lines should work independently, but both of them together cannot be stopped!
var hideKeyboard = function() {
 document.activeElement.blur();
 $("input").blur();
};
UPDATE: As a modern replacement for the jquery statement above, here's a simple, modern alternative:
var hideKeyboard = function() {
 document.activeElement.blur();
 var inputs = document.querySelectorAll('input');
 for(var i=0; i < inputs.length; i++) {
  inputs[i].blur();
 }
};

September 25, 2011

Given distance and friction, calculate the initial velocity to stop at the distance target

While simulating iOS scroll views in javascript, I needed to find the initial velocity to send the scroll content back into position if you drag it out of bounds. Since the in-bounds scroll view inertia was already working with a given friction, I wanted to use this to run the calculation. After some brute-force experimenting, I came up with the equation to calculate this velocity:
// set up vars
var curPos = 0;
var distance = 2000;
var friction = 0.8;
var velocity = distance / ( friction * ( 1 / ( 1 - friction ) ) );

// in timer:
setInterval(function(){
    velocity *= friction;
    curPos += velocity;
},30);
The curPos value will ease into the target distance, no matter what you set as the friction and distance. Friction must be a decimal between zero and one.

September 1, 2011

Google Maps API: Polyline encoding/decoding issue

I'm using google's Geometry Library to encode large numbers of GPS location points into a compressed format for storage. You can add this capability to your .js by adding it to the quersystring in the javascript reference:
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?libraries=geometry&sensor=true"></script>
The library provides a very easy way to display a path, add points realtime, store this path as an encoded & compressed string, and feed that string back into a new map when you're ready to display it later. You can see a live example here.

Encoding works great, but I ran into an issue where some of my encoded paths, generated by google.maps.geometry.encoding.encodePath() from my google.maps.Polyline object, would have major errors when using the google.maps.geometry.encoding.decodePath() method. My Polyline would have random right-angle turns that effectively ruined my path. I played around with the encoded string, trying to figure out what was causing the issue, to no avail. I found another implementation of the polyline encoding algorithm, and found an explanation of what was causing the issue.

It turns out that you need to escape the backslashes that may appear in the output string from google.maps.geometry.encoding.encodePath() (and the other library linked to above). So if you're storing the string for later, you want to do something like this:
var encodedPath = google.maps.geometry.encoding.encodePath( _poly.getPath() ).replace(/\\/g,'\\\\');
You can then feed that encoded string into a new Map's Polyline instance like so:
_poly.setPath( google.maps.geometry.encoding.decodePath( encodedPath ) );
It seems like an oversight that this double-backslash issue isn't mentioned in the Google documentation. I spent hours before trying to figure out the problem in my mobile app before coming across the fix.

Finally, after you set a decoded path as the data for a Polyline, use the following code to fit the Map to the bounds of your path:
var bounds = new google.maps.LatLngBounds();
var path = _poly.getPath();
path.forEach(function( latlng ) {
	bounds.extend( latlng );
});
_map.fitBounds( bounds );	

July 26, 2011

Javascript: Formatting latitude/longitude location between Decimal and DMS (degrees, minutes, seconds)

I'm working on a javascript UI for a mobile app that receives location data in the Decmial format. We wanted the fancy DMS format, so I found some code, rewrote it, and wrapped it up into a nice little static class for converting back and forth.
// A static class for converting between Decimal and DMS formats for a location
// ported from: http://andrew.hedges.name/experiments/convert_lat_long/
// Decimal Degrees = Degrees + minutes/60 + seconds/3600
// more info on formats here: http://www.maptools.com/UsingLatLon/Formats.html
// use: LocationFormatter.DMSToDecimal( 45, 35, 38, LocationFormatter.SOUTH );
// or:  LocationFormatter.decimalToDMS( -45.59389 );

function LocationFormatter(){
};

LocationFormatter.NORTH = 'N';
LocationFormatter.SOUTH = 'S';
LocationFormatter.EAST = 'E';
LocationFormatter.WEST = 'W';

LocationFormatter.roundToDecimal = function( inputNum, numPoints ) {
 var multiplier = Math.pow( 10, numPoints );
 return Math.round( inputNum * multiplier ) / multiplier;
};

LocationFormatter.decimalToDMS = function( location, hemisphere ){
 if( location < 0 ) location *= -1; // strip dash '-'
 
 var degrees = Math.floor( location );          // strip decimal remainer for degrees
 var minutesFromRemainder = ( location - degrees ) * 60;       // multiply the remainer by 60
 var minutes = Math.floor( minutesFromRemainder );       // get minutes from integer
 var secondsFromRemainder = ( minutesFromRemainder - minutes ) * 60;   // multiply the remainer by 60
 var seconds = LocationFormatter.roundToDecimal( secondsFromRemainder, 2 ); // get minutes by rounding to integer

 return degrees + '° ' + minutes + "' " + seconds + '" ' + hemisphere;
};

LocationFormatter.decimalLatToDMS = function( location ){
 var hemisphere = ( location < 0 ) ? LocationFormatter.SOUTH : LocationFormatter.NORTH; // south if negative
 return LocationFormatter.decimalToDMS( location, hemisphere );
};

LocationFormatter.decimalLongToDMS = function( location ){
 var hemisphere = ( location < 0 ) ? LocationFormatter.WEST : LocationFormatter.EAST;  // west if negative
 return LocationFormatter.decimalToDMS( location, hemisphere );
};

LocationFormatter.DMSToDecimal = function( degrees, minutes, seconds, hemisphere ){
 var ddVal = degrees + minutes / 60 + seconds / 3600;
 ddVal = ( hemisphere == LocationFormatter.SOUTH || hemisphere == LocationFormatter.WEST ) ? ddVal * -1 : ddVal;
 return LocationFormatter.roundToDecimal( ddVal, 5 );  
};

February 21, 2011

jQuery fadeIn() bug on iPad: element disappears

I'm using jQuery on a new site, and a fadeIn() animation broke, but only on the iPad with iOS 3.2.2. It works fine in every other browser, including the newer iOS (4+) for iPad. To fix the problem, which in my case doesn't affect other browsers, I simply added a callback function that manually sets the width and height of the element:
newSection.fadeIn( 300 , function(){
  newSection.css( { width:320, height:480 } );
});
I tried other css properties first, but width and height are the magic properties that prevent the element from being hidden. Quick, someone tell Steve Jobs :p

January 19, 2011

Javascript: Clear a webkitTransition animation

I was animating an html element with Webkit transitions via javascript, and after the animation was done, I applied non-animated webkitTransform positioning to the same element. It animated instead of immediately displaying the new style. I came up with the following function to clear any previous Webkit animation values:
function clearAnimation( element ) {
  if( typeof element.style.webkitTransform !== 'undefined' && element.style.webkitTransform ) {   // 2nd conditional fixes bug in Chrome on windows
    element.style.webkitTransition = '';
    element.style.webkitTransform = '';
  }
}
This will prevent any overlap when switching between animatiions and instant repositioning.