JavaScript: isScrolledIntoView


I needed a simple way to detect when a placeholder DIV (that would contain an image) had scrolled into the current viewport of the browser. I’ve seen a few solutions that worked, and a few that didn’t (hello? test your code!). Here’s my simple solution:

function isScrolledIntoView(elem) {
    elem = $(elem);
    var win = $(window);
    var docViewTop = win.scrollTop();
    var docViewBottom = docViewTop + win.height();
    var elemTop = elem.offset().top;
    var elemBottom = elemTop + elem.height();

    var a = (elemTop > docViewBottom);
    if (a) { return false; }
    var b = (elemBottom > docViewTop);
    if (!b) { return false; }
    return !(a && b);

Nothing fancy, except it does require jQuery.

When one of the place holder DIVs scrolled into view, the code would trigger a queued load of the image.


As the SmugMug API is poorly designed in a few places, in particular as it relates to showing a thumbnail for a gallery/album, when an album/gallery is scrolled into view, the app loads a list of ALL of the images from the now visible album and selects one of them to show as the thumbnail. It’s extremely inefficient unfortunately. Instead, if they could have included that extra piece of data in the gallery list (thumbnail image and thumbnail image key), boom!

I’ve also included a delay so that the auto loading only occurs after a 1 second pause (either the window being resized or scrolling occurring):

function delayLoadNewVisible() {
    if (visibilityDelayTimerId == 0) {
        visibilityDelayTimerId = window.setTimeout(function () {
            visibilityDelayTimerId = 0;
        }, 1000);

I’m not going to post the code for the other useful aspect of this functionality, but I’ll tell you about it, and leave coding it as an exercise for the reader. Smile

When the user scrolls new albums into view, after the pause, they’re added to a load queue. However, as it’s just as likely that the user will scroll the album off the screen, my load code also can remove something from the load queue if it hasn’t been loaded already. This way, visible albums are given precedence. By using the queue in this way, there are a manageable number of outstanding requests at any given time, and ideally only those that are relevant to what’s on the user’s screen.

Some code from SnugUp.Browser (an album browser for SmugMug)

I’ve been doing some tinkering recently with SmugMug again.

Through testing SnugUp for the past 4 years, I’ve made quite the mess of my SmugMug account. Literally hundreds of poorly organized and often completely junk albums. I’ve been wanting to clean it up, but SmugMug’s UI for that is so obnoxiously slow and tedious that I decided I wanted to write a tool to make it easier to manage.

Admittedly, given the amount of time I’ve spent on writing the tool (which I’m not yet finished with), I could have cleaned up my SmugMug account dozens of times. However, with my developer hat on, I thought, how fun would that be? Smile



So, I’ve started to create the SnugUp.Browser. It’s a bit of an interesting beast in that it is a Windows application that hosts a web browser to display its UI. I’d tried using WPF/XAML and just couldn’t get the look I was wanting in a reasonable amount of time and effort.

<RANT>WPF desperately needs a VirtualizingWrapPanel. Microsoft needs to ship it. </RANT>

So, I created a WinForms project to host the IE Web Browser. (Arrgh, the WPF WebBrowser sucks still. It’s not nearly as feature complete as the one from WinForms!). Internally, the web pages are served via an HttpListener.

try { int portSuggest = GetAvailablePort(); portSuggest = 40000; _listener.Prefixes.Add(string.Format("http://localhost:{0}/", portSuggest)); _listener.Start(); while (maxConnections-- > 0) { _listener.BeginGetContext(HandleRequest, _listener); } } catch (Exception ex) { Debug.WriteLine(ex); }

The HandleRequest method responds in two ways:

  1. Respond with JSON data (as if it were a web service). 99% of the code needed to access SmugMug’s APIs was already written in a C# library I wrote for SnugUp.
  2. Respond with binary data, providing a “proxy” to a service. This was needed to handle downloading images from the application. When the request was directly made from the WebBrowser to SmugMug, it was refused as the http-referer header was not a valid source apparently (SmugMug didn’t like “http://localhost:####” as the referrer.
resourceRequested = context.Request.Url.LocalPath.Replace('/', '.'); if (resourceRequested.StartsWith(".")) { resourceRequested = resourceRequested.Substring(1); } Debug.WriteLine(string.Format("Requested: {0}", resourceRequested)); using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(string.Format("SnugUp.Resources.{0}", resourceRequested))) { if (stream == null) { //context.Response.StatusCode = 404; if (url.LocalPath.StartsWith("/proxy")) { var proxyUrl = url.Query.Substring("?url=".Length); extension = Path.GetFileName(proxyUrl); try { WebClient client = new WebClient(); byte[] buffer = client.DownloadData(proxyUrl); context.Response.Headers.Add("Expires", DateTime.UtcNow.AddDays(30).ToString("R")); context.Response.ContentType = GetContentType(context, extension); context.Response.OutputStream.Write(buffer, 0, buffer.Length); } catch { context.Response.StatusCode = 404; } } } else { string contentType = GetContentType(context, extension); context.Response.ContentType = contentType; stream.CopyTo(context.Response.OutputStream); } // Close the Response to send it to the client. // } }

If the resource can’t be found as an Embedded Resource, it tries a proxy.

I built a tiny router for the web services so that I could easily plug-n-play new functionality:

public virtual object Route(string path, dynamic data) { if (path.StartsWith("/")) { path = path.Substring(1); } var paths = path.Split('/'); if (paths.Length > 0) { var controllerName = paths[0]; var controllerType = Type.GetType(string.Format("SnugUp.Controller.{0}Controller", controllerName), false, true); if (controllerType != null) { var instance = InitializeControllerInstance(controllerType); // default? if (paths.Length > 1) { MethodInfo method = controllerType.GetMethod(paths[1]); if (method != null) { try { object results = method.Invoke(instance, new object[] {data}); return results; } catch (Exception ex) { Debug.WriteLine(ex); } } } } } return null; }

It just looks up a request dynamically, maps to a method, and calls it (the response is eventually serialized as JSON).

For UI, I’m currently using Bootstrap (although that may be removed), Knockout.JS, and jQuery. I’m doing a bit of trickery to make sure that only what’s visible on the screen is loaded (even when the user scrolls up and down … it’s pretty smart).  I’ll likely post more about that in a future update.

The app is becoming larger… and bloated a bit when compared to my original needs. Smile



It now can show the images from the gallery as well. Smile


Announcing SnugUp version 2

imageMore than a few years ago, I created SnugUp version 1, which is a handy way of synchronizing folders of images with SmugMug for Windows users. I’ve made a number of changes in the last month based on some requests and the result is a significant update (yet the core features are all there – just improved!).

New features:

  • (Changed to version 2)
  • Completely revamped look and feel (which did remove a few ‘flashy’ features)
  • Uses Click once for application updating (which should make it easier to push updates and bug fixes out to users)
  • Added settings for automatic upload (when application starts)
  • Added setting for Subcategory selection (only selection, no creation of subcategories through SnugUp)
  • Added setting for custom gallery naming
  • Completely changed settings user interface to more logically group and explain settings
  • Fixed a number of bugs (that shouldn’t have appeared to user anyway!)
  • Added support for new file extensions (to upload videos in particular)

It does require .NET 4.0 Framework Client profile (which likely you’ve already got on your machine, but if you don’t the installer SHOULD make it easy to download).

Go here ( for more information and to download. I’d recommend uninstalling the old version first.