Tuesday, June 24, 2014

Simplifying the Terms of Service

At the GDISF study group last night, one of the attendees was working on the Terms of Service for a new site, and she wanted to give the users a way to see the layman-friendly version of the terms. She was inspired by the "Simply Put" right hand side of the Pinterest site, and was experimenting with other ways to present that.

Pinterest:

I'd never seen that sort of thing before, and I think it's pretty neat, so I asked on Twitter for more examples. Here are a few they shared:

500px:

CodePen.io:

Aviary:


Related to this, there's the Terms of Service; Didn't Read initiative that comes up with layman-friendly version of the top TOSes, and there's a list of related efforts to simplify terms and identify ambigious terms from CommonTerms.

How do you make your Terms of Service layman-friendly? Tell me in the comments!

Thursday, February 27, 2014

The Long Lunch Table

For the last two years, I've had the pleasure of working for companies that are still small enough to fit in a room; small enough so that everyone can squeeze at the same lunch table. Even at Khan Academy, where we now have almost 50 people in the office, we have a long table made up of many tables, squished together so as to appear a continuous structure. I never really thought about that until today, when we were talking about plans for our new office.

You see, our small company is getting bigger, and we're moving into a larger space. We have to decide which of the traditions and configurations to bring over from our current space, and which to leave behind as a thing-we-did-when-we-were-smaller. One of those is the team lunch. Our office admin announced to all of us that they're working hard to make sure that we can continue to all eat lunch together. A devil's advocate amongst us responded with "Why? Why do we need to eat lunch at the same time and same table if we can only hear a 4 person radius around us?" He had a good point, yet he also provoked a visible gut reaction in many. But, why? What reasons did we come up with?

We kerfuffled for a bit, trying to justify our insanity on holding onto our irrational needs, until my colleague spoke up. He told the story of lunch at his last company, which was a few times larger. They had a long lunch period, with many small lunch tables in a big cafeteria, and employees were expected to lunch at different times, to distribute the lines over time. Employees would walk in, look around at the lunch tables, not see anyone they knew - or not see anyone they felt confident in table-crashing - and go back to their desks. Eventually, many of them defaulted to eating lunch at their desks. Lunch had gone from being a social affair to being a daily reminder of loneliness.

In fact, when he recalled this, I remembered feeling the same way at Google. Sure, I would know people scattered around the tables in the cafeteria, but it took guts to feel like I could impose myself on any given table. There were days that I couldn't muster up enough of those guts; and those were the saddest.

And now, finally, I realize the simple brilliance of the long lunch table and the single lunch period. When you get your food, you don't have to gather the confidence to approach a table - you just look at the long table, pick the next logical available seat, and sit there. That's what everybody does, and there's no expectation that you have to ask to do that, because, what else would you do?

Maybe the long lunch table is why I look forward so much to lunch every day at Khan Academy. It's certainly not that I dislike my work, but I adore our lunches: I don't have to stress about where to sit, and I know that wherever I sit, I'll be surrounded by some subset of my smart and funny colleagues.

Here's to hoping that we can keep our lunch table long for as long as possible.

Thursday, January 30, 2014

Improving front page performance: removing images, 5 ways

We regularly hold Fix-Its at Khan Academy, where we take a day to work on the little things that pile up, the bugs and tweaks and optimizations we don't find the time to do otherwise. On Tuesday, we had a Fix-It dedicated entirely to performance, and we made a Trello board of performance improvement ideas, many of them around speeding up our server-side calls.

I decided to tackle the frontend, starting with the very first experience a user has on Khan Academy: the logged out landing page. My colleague told me there were some images that could be delay loaded on it, a technique that I'm familiar with, so I logged out, opened up the Chrome Dev Tools Network panel, and checked out the HTTP requests (particularly the pre-DOMContentLoaded ones). Sure enough, lots of image requests!

Here's a screenshot of the page, with the various images circled in red:



Some of those images are "above the fold", meaning they're visible without scrolling, while others are "below the fold", requiring varying degrees of scrolling to see. This is an important distinction to make, because most users do not scroll on our landing page - most will go straight to signup, which is what we want to happen, and have optimized for in numerous A/B tests. Depending on their position on the page, I used different techniques to deal with each image. Let's go through them, shall we?



TinyPNG

For many of the techniques below, I first put the images through TinyPNG to optimize their size, and often achieved 50%-75% compression, without discernible loss in image quality. Plus, I made a Panda happy!


Top Logo: CSS Data URI

HTTP Requests saved: 1

The top logo is displayed on every Khan Academy webpage, so it will need to get downloaded by ever user in some form. Since we're already sending down a CSS file on every page, and the logo is a fairly small file (2KB, after compression), my colleague suggested just inlining it as data URI in the CSS file.

Thanks to the magic of all of our conversations happening in HipChat, I then found out that my colleague Craig Silverstein actually made it so that our CSS build process will automatically Data URI an image that's used as a background-image, as long as it's sufficiently small size and only included in one CSS package or if it has a /* data-uri */ comment next to it. If I hadn't had that option, I would have used one of the online converters - but I'm happy I get to keep our CSS files clean and readable!


Login icons: Sprites FontAwesome

HTTP Requests saved: 2

CSS sprites are a now classic technique for saving HTTP requests for the commonly used little images on your site, condensing them all into one image and using background-image/background-position magic. That seemed like the obvious approach at first for the remaining above-the-fold images on the page, the login icons and the purple leaves background. I optimized the images, uploaded them to this online CSS Sprite generator, and set their new CSS rules, happily turning 3 HTTP requests into 1.

However, my colleague then informed me that we actually had FontAwesome icons (in our custom build!) that looked nearly the same as the Google and Facebook login icons, and he'd been meaning to change those over. We already send down FontAwesome with every page, because we use their icons so heavily, so it made sense to give up images entirely and just use the FontAwesome classes.


Featured Images: Delay Load

HTTP Requests saved: 4

We display three images related to featured content just below the fold, which we don't want to sprite because we change them up a fair bit, but we also don't want to load for all the non-scrolling users out there. So I implemented this delay-loading technique for the images, which basically entails adding a scroll handler, checking if the desired DOM elements are within view or within some threshold of being in view, and swapping out their background-image or src if so, based on data attribute values.

Here's the generalized delay loading code that I wrote as a simple jQuery plugin:

Here's example HTML for one of the images:

<div data-delayed-bgimage="https://www.kastatic.org/images/featured-actions/pollock.jpg">
</div>

And here's the JS that calls the delay loading plugin for those images:

var maybeLoadMedia = function(nearThreshold) {
    $("#homepage [data-delayed-bgimage]").each(function() {
        $(this).delayLoad(nearThreshold);
    });
};

// On scroll, use very large threshold. Most people don't scroll homepage,
// but if they do, we want them to see everything.
$(window).on("scroll.load-media", _.throttle(function() {
    maybeLoadMedia(600);
}, 100));

// Start with no threshold, items must be visible
maybeLoadMedia(0);

Youtube Video: Delay Load

HTTP Requests saved: 1+

Way below the fold, we embed an iframe of Sal's TED talk. It's a great talk, but once again, most users won't make it this far. We use the same technique as for the images, with one difference: for img tags, we start them off with an empty src attribute. However, that's not a valid value for src for the iframe, so we start the iframe off with src="about:blank". Ben Vinegar, an iframe wrangling expert from Disqus, suggests it's even better to completely create the iframe from scratch, but since the src swapping worked, I went the simpler route this time.

Here's what the HTML looked like:

<iframe width="560" height="315" frameborder="0" allowfullscreen
  data-delayed-src="//www.youtube.com/embed/gM95HHI4gLk" src="about:blank">
</iframe>

And the JS was pretty much the same:

var maybeLoadMedia = function(nearThreshold) {
    $("#homepage .youtube-video iframe").each(function() {
        $(this).delayLoad(nearThreshold);
    });
};

$(window).on("scroll.load-media", _.throttle(function() {
    maybeLoadMedia(600);
}, 100));
maybeLoadMedia(0);

Social Icons: Delay Load

HTTP Requests saved: 0 (all were async tags)

We also display a host of social icons next to the video, and we have to load in the relevant JS APIs for each of those, often via dynamically appending a script tag. We set the async attribute on all of those, so they were not affecting our DOMContentLoaded time, but hey, might as well delay load them! No sense in making users do HTTP requests for things they won't need.

var maybeLoadSocialMedia = function(nearThreshold) {
    if ($("#homepage #social-actions").inView(nearThreshold)) {
        _.defer(function() {
            Homepage.initSocialButtons();
        });
        $(window).off("scroll.load-social-media");
    }
};

$(window).on("scroll.load-social-media", _.throttle(function() {
    maybeLoadSocialMedia(1000);
}, 100));

Subscribe icon: FontAwesome

HTTP Requests saved: 1

I started off by delay loading the RSS subscribe icon, but it felt like overkill and I realized there was a much easier approach: just use the FontAwesome subscribe icon, and color it orange. Ta-da, nearly the same!

It was easy to do with this HTML:

<span class="icon-rss-sign"></span>

...and this CSS

.icon-rss-sign {
  color: orange;
  text-decoration: none;
  margin-right: 5px;
  font-size: 18px;
}

Invisible image: Delete!

HTTP Requests saved: 1

After I'd optimized all the images I could see, there was still an HTTP request for an image that I couldn't ever see: our loading gif. I spent a while trying to track down what code was loading it, to see if it was validly being used at some point during the page rendering process. I finally discovered that it was in a CSS rule for an element that was visible on the page - but its visibility was set to hidden. Apparently, browsers (or at least Chrome) will download background-image's for hidden elements. Luckily, I then discovered that it was "dead" CSS - the rule that would toggle its visibility was never getting triggered. We'd stopped using the loading image without removing the associated CSS rules, and that was costing an extra HTTP request. I happily and promptly deleted it, whee.



The Exciting Results!

If you've been keeping score, my changes removed at least 10 HTTP requests, if not more (like additional ones triggered from the Youtube iframe). I recorded load times across 5 page loads in Chrome Dev Tools both before and after the changes, and here are the results:

# of Requests DOMContentLoaded (average) load (average Time between them (average)
BEFORE 65 3.02 5.66 2.64
AFTER 28 1.86 2.00 0.14
% REDUCED 57% 39% 65% 95%

The full spreadsheet is here, and I've also downloaded the HARs from all the loads.

There is still much we can improve - there's a lot of latency and variability in the initial download of the HTML page itself, plus we're passing down several CSS, JS, and font files. But still, it's impressive what we could do in a single day, with just the images. Thanks to those techniques, the page makes it to the DOMContentLoaded event in nearly half the time, and to the load event in less than half the time.

Got any other techniques to share, or tips for how you improve your site's performance? Tell us in the comments!

Saturday, January 25, 2014

Networking at events: It should be easy, not scary

I was at an event the other night and the MC said, "I hope you like networking, because that's what we're here for!" My immediate thought was "Uhhhhhh, no. Networking is the most frightening thing I could ever imagine."

See, when I think about "networking," I picture a room full of people that have to come up with excuses to talk to each other, come up with excuses to stop talking to each other when the conversation has peaked, and keep doing this for hours on end. I can't decide which aspect of that is the worst, because it's all the worst. There are good parts in the middle, when you've discovered your commonality with your current conversational target and you figure out how you can both benefit from learning about each other, but those good parts are sandwiched by anxiety-inducing awkward social situations.

Fortunately, I'm not the only one who finds it hard to approach large numbers of random strangers, and I've been happy to come across multiple forms of more structured networking at recent events. I wanted to highlight a few of them here in this post, to put out ideas for the event organizers out there, and to hopefully find out about other approaches in the comments.

Introductions

I went to an EdSurge Womens Night last fall, and when I walked into the room full of people that I didn't know, much of them in the thick of conversations, I had a shyness attack. I immediately found a corner that wasn't visible from the rest of the room, and busied myself with my phone. And yes, I may have tweet-ranted my feelings a bit, as well. Once I get shy like that, I find it increasingly hard to recover, to muster up the courage to impose myself on someone - so I was ready to call it a failed night and go home.

But then the organizer shushed everyone, gave a little spiel about the event purpose and sponsors, and then encouraged us to give little spiels. She wanted us to let everyone know what we were there for - like if we were looking to hire, looking for a job, looking for partners. She didn't make everyone do it (which would have taken a while) but was encouraging enough that about a third of us took the mic for thirty seconds. I was one of those that gave a spiel, because I knew that I'm the kind of person that needs other people to approach me, especially in the shy state I was in then. After that, we all went back to mingling, but now we all knew a lot more about people that were there and had real reasons to approach each other. I was then busy the entire rest of the night, speaking with a variety of people who were interested in hearing more about what I was up to.

This will not work for every event, due to the time it takes -- but if the event is small enough, a simple round of introductions can do wonders for improving networking at events, for lowering the barrier to approach. There's still the problem of the social awkwardness of leaving a conversation, though, which is where the next strategies come in.

Speed Networking, Pre-Matched

Early this year, I attended an NSF conference for ~300 computer science education researchers. I'm new to the more academic circles, after being so heavy in the "industry" circles for so long, so I knew only a handful of the researchers. It was great getting to catch up with them, but I also wanted to meet a few new folks.

Thankfully, the conference organizers really wanted this gathering to be heavy on connecting and conversing with each other, starting with an unconference style approach to picking session topics and carving out time for 5 attendee-led discussion sessions.

On the first day, they announced a special session right after lunch: speed networking. Before the conference, we had all filled out our particular research interests on the registration website. They ran those through an algorithm to pick pairs of similar attendees, and printed a list of 8 numbers on the back of our name tag, each number corresponding to a pair. When we arrived at the room for the session, we found 150 pairs of chairs with numbers on them, and we sat in the chair with our first number printed on it. For the next hour, we rotated every 8 minutes and met someone completely new each time. It was a great way to meet people I wouldn't have otherwise met, and I found that 8 minutes was enough time to have enough of a conversation to figure out if we should continue talking later (or just to learn something new and move on).

There are a few logistical issues with this sort of session, since not everyone who signs up will end up attending and since the organizers have to pre-calculate the pairs. I had actually signed up too late, after they'd printed the badges, but I just grabbed the badge of someone who seemed similar to me but didn't make it to the conference, and I was happy with my pairs. And in the case that nobody sat across from me, I'd just triple up with people near me. So as it turned out, the logistical issues were quite surmountable, and I think a speed networking session like that would be a fun addition to many conferences.

Speed Networking, Randomized

Just this week, I attended a networking event put on by Berkeley Women in Science. I came as one of the "professional"s, expecting to meet many non-professional students. I was a bit reluctant when they first asked me to attend, because I heard the title "networking" and thought, "oh no, what if they expect *me* to approach people, because I'm so professional?" I expressed my doubts that I would be able to do that effectively and only decided to go when they reassured me that they would be facilitating networking via introductions and other means.

The event started with the MC introducing each of the professionals by name and title, and then we started a speed networking session of sorts. We were instructed to find someone that we hadn't met yet, that was the opposite of us (i.e. I found students because I was a professional). Then they gave us a question that the left partner would answer for 2 minutes and then another question that the right partner would answer. The questions would ask about how we got into STEM, the stereotypes we encountered, ideas for countering them. This went on for about four rounds, and then we went into free networking.

This was an interesting approach because they gave us the questions, removing the pressure of coming up with conversation starters. There were some drawbacks as well, however -- 1) since we found our pairs randomly, we were less likely to meet the most relevant people (I ended up chatting with many microbiologists and no Computer Science majors, which was interesting but non-optimal from a recruiting angle), 2) we didn't have any time for introductions, so we ended up trying to do that quickly or tying it into our answers, and 3) the MCs had to keep shushing everyone to move on to the next question, because people had more to say than they could cover in 2 minutes. I think this could work well with modifications, like having a 2 minute introduction time, and it'd work well at an event where everyone is relevant to each other's interests.