#justcode

Tinderesque – building a Tinder-like interface with CSS animations and vanilla JS #justcode

Tinder is a very successful dating app, and one of its features is a way to say yes or no to prospective partners by swiping right or left or pressing a yes or no button. The interface is cards that drop when you press the buttons.

As with any successful interface, a lot of clones that mimick them happen very fast. One of those is FontFlame – a Tinder for Font Pairings. When I saw this one, I thought the animation isn’t right (it just moves to the right or left and fades, there’s no turning or popping up). I tried to fix the CSS animation to match more closely what Tinder is doing, but to my dismay I found out that whilst Fontflame uses CSS animations, they get over-ridden by jQuery ones. I contacted the author and offered my CSS animation to replace the current one.

Just for fun, I packed this up into a quick solution consisting of a CSS animation and some JavaScript to control the voting process.

Tinderesque in action

I called it Tinderesque. You can see it in action, Get the code and read the instructions how to use it on GitHub.

Here’s some explanations on how Tinderesque works.

The Tinderesque animation

Animating the cards is no rocket science: we rotate the card after setting the transformation origin to the bottom of the card and shift it up a bit to get a “discard” effect.

First up, we need to define the HTML of the collection of cards we want to vote on. This should be pretty straight forward:

<div class="cardcontainer list">
  <ul class="cardlist">
    <li class="card current">#1</li>
    <li class="card">#2</li>
    <li class="card">#3</li>
    <li class="card">#4</li>
    <li class="card">#5</li>
    <li class="card">#6</li>
  </ul>
  <button class="but-nope">X</button>
  <button class="but-yay">?</button>
</div>

To achieve the animation effect we need to give the card we want to animate some dimensions and set its transform origin to its bottom:

.card {
  width: 150px;
  height: 200px;
  display: block;
  background: #666;
  transform-origin: 50% 99%;
}

This ensures that the card doesn’t get rotated around its centre but the bottom instead.

For the positive scenario, we rotate the card clockwise and push it up a bit to get the discard effect. This can be done using a rotate and translateY transformation. We also animate the opacity of the card from 1 to 0, effectively hiding it.

@keyframes yay {
  from {
    transform: rotate(0deg);
    opacity: 1;
  }
  to {
    transform: rotate(40deg) translateY(-80px);
    opacity: 0;
  }
}

For the negative use case, we rotate the card counter-clockwise:

@keyframes nope {
  from {
    transform: rotate(0deg);
    opacity: 1;
  }
  to {
    transform: rotate(-40deg) translateY(-80px);
    opacity: 0;
  }
}

We then trigger these animations by adding and removing classes on the parent elmement of the cards:

.cardcontainer.yes .card {
  animation: yay 0.7s ease-out;
}
.cardcontainer.nope .card {
  animation: nope 0.7s ease-out;
}

Going through the whole card deck

In order to go through the card deck two things must happen:

  • We need to animate the current card using the positive or negative animation
  • When the animation is finished, we need to remove the card from the document and show the next one.

By default, we hide all the cards in the deck. Only the one with the class of current is visible:

.list .card {
  display: none;
}
.list .current {
  display: block;
}

This means that we need to shift the class of current to the next card once this one has been approved or discared. But first, we need to trigger the animation. In order to achieve this, we need either a hover or some clever trickery with checkboxes in CSS. It is more extensible though to use JavaScript.

Triggering the animations

All we need to trigger the animations is adding an event handler attached to the buttons in the HTML. Depending on which button was pressed we add a yes or nope class to the parent element of the button – in this case, the cardcontainer DIV.

function animatecard(ev) {
  var t = ev.target;
  if (t.className === 'but-nope') {
    t.parentNode.classList.add('nope');
  }
  if (t.className === 'but-yay') {
    t.parentNode.classList.add('yes');
  }
}
document.body.addEventListener('click', animatecard);

We’re using event delegation here with a click handler on the body of the document. We can of course extend this to pointer or touch handlers to allow for touch events and simulating swipe gestures.

Acting after the animation using events

Our card has now been animated and is invisible, but it is still in the document and the next card isn’t visible yet. We need to remove the card and shift the current class to the next card in the deck.

Every CSS animation has an animationend event we can use for that. The event gives us the name of the event, which gives us the name of the class to remove.

function animationdone(ev) {
 
  // get the container
  var origin = ev.target.parentNode;
 
  // remove the appropriate class
  // depending on the animation name
  if (ev.animationName === 'yay') {
    origin.classList.remove('yes');
  }
  if (ev.animationName === 'nope') {
    origin.classList.remove('nope');
  }
 
  // if any of the card events have 
  // ended…
  if (ev.animationName === 'nope' ||
      ev.animationName === 'yay') {
 
  // remove the first card in the element
    origin.querySelector('.current').remove();
 
  // if there are no cards left, do nothing
    if (!origin.querySelector('.card')) {
      // no more cards left - 
      // TODO other functionality
    } else {
 
  // otherwise shift the 'current' class to 
  // the next card 
      origin.querySelector('.card').
        classList.add('current');
    }
  }
}
document.body.addEventListener(
  'animationend', animationdone
);

That’s more or less all we need to do. Except that Safari still hasn’t joined us in the year 2015. That’s why we need to repeat both the CSS animation definitions in our CSS with –webkit– prefixes and add an event handler for webkitAnimationEnd. I refuse to do so here, as this is depressing, but you can see it in the tinderesque source code.

Extending the functionality using Custom Events

The functionality now is pretty basic, which means we want to make it as easy as possible to extend it. The simplest way to do that is to add Custom Events that fire when things happen to our card deck. This is as easy as using this function:

function fireCustomEvent(name, payload) {
  var newevent = new CustomEvent(name, {
    detail: payload
  });
  document.body.dispatchEvent(newevent);
}

Custom events can get a payload – you can define what the listener gets as parameters. In the case of tinderesque, the animatecard function has been extended to send a reference to the button that was clicked, its container element (in case you have several decks) and a copy of the current card.

function animatecard(ev) {
  var t = ev.target;
  if (t.className === 'but-nope') {
    t.parentNode.classList.add('nope');
    fireCustomEvent('nopecard',
      {
        origin: t,
        container: t.parentNode,
        card: t.parentNode.querySelector('.card')
      }
    );
  }
  if (t.className === 'but-yay') {
    t.parentNode.classList.add('yes');
    fireCustomEvent('yepcard',
      {
        origin: t,
        container: t.parentNode,
        card: t.parentNode.querySelector('.card')
      }
    );
  }
}

This allows you to read the content of the card before it gets removed from the document.

Reading cards before they get discarded

var b = document.body;
b.addEventListener('nopecard', function(ev) {
  console.log(
    ev.detail.card,
    ev.detail.card.innerHTML
  );
});
b.addEventListener('yepcard', function(ev) {
  console.log(
    ev.detail.card,
    ev.detail.card.innerHTML
  );
});

Tinderesque also fires a custom event called deckempty when the last card got removed from the list. In the demo page this is used to re-stack the deck:

var b = document.body;
b.addEventListener('deckempty', function(ev) {
  var container = ev.detail.container;
  var list = container.querySelector('.cardlist');
  var out = '<li class="card current">#1</li>';
  for (var i = 2; i < 6; i++) {
    out += '<li class="card">#' + i + '</li>';
  }
  list.innerHTML = out;
});

This should get you well on the way to build your own Tinder-like interface. If I find the time, I will record a screencast doing exactly that.

Cuter – a quick demo using Tinderesque

As a demo, I put together Cuter – a way to say yes or no to cute animal pictures and end up with a list of the ones you chose.
You can see it on YouTube:

View full post on Christian Heilmann

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.22_1171]
Rating: 0 (from 0 votes)

#justcode

As developers we are incredibly lucky. We work in a very growing and immensely well paid market, our companies shower us with benefits, companies offer us jobs rather than having to send out hundreds of CVs on the off-chance and even the mass media and politicians start talking about “coding” being a skill everybody needs.

Quite some part of this success is based on the stubbornness we showed in the past. When we got a task to build something we didn’t give up on it and said it is impossible. Instead we went back in our corner and tried and failed and tried again with sparks flying and code explosions happening until we achieved what we wanted. Think Dr. Bunsen Honedew’s laboratory instead of Statler and Waldorf.

This gave especially the web a strange “hack it together” reputation that many people keep bringing up when it comes to replacing JavaScript for example with “more organised and professional” languages. But you know what? I really think when it comes to the web, this is its main strength.

The fun of coding

whimsy

As explained earlier in my Flash is not the enemy post, whimsy and spontaneous ideas is what made the web a larger media outlet than it was. It wasn’t the large sites that got non-technical people excited. It was the funny animation and short-lived game that you could mail to your friends.

Therefore I think it is important to celebrate this for yourself from time to time. Personally I find myself extremely lucky to have been at the right time (and moving around to the right places) when the web exploded into an offering of amazingly cool things and while I am sure as hell not proud of the code I had to write to get things done in the past, I am happy that I did and that I didn’t give up or wait until someone else solves my problems for me.

Having just taught a workshop on HTML5 at Industryconf I found that we are losing a bit on that. Attendees were worried that they need to learn a lot of libraries and find the right plugins to get started and once shown that they have the power to do most of what they want using the things browsers come with out of the box got quickly into enjoying themselves reaching new levels.

One thing I did with the attendees is a To-Do List App in plain HTML/JS/CSS (No sound).

This is what the Mozilla Webmaker Project is about – to get non-programmers excited about building things for the web. And it is incredibly exciting to see some of these events as a “professional”.

I think it is very important to never forget about the wonder we experienced the first time we made something show up on screen or wrote our first condition that printed out “is amazing” when you entered your name or “is boring” when it was another one.

Be fearless

A lot of times being creative means being fearless. Watching Bret Victor’s talks and seeing his Learnable programming course and Seb Lee-Delisle’s training courses they consist of one main thing – play with things and worry about them breaking later. Amazing results happen when the outcome and the input get as close together as possible – not when things happen using dozens of abstractions.

This does not have to be visual from the get-go though. The MPEG-1 decoder in pure JavaScript for example is pure byte-shifting but blew me away in its fearlessness of what could go wrong.

Go, code!

Why not have a go? Take 10 minutes, half and hour, an hour out of your life right now and use it to #justcode something, anything. Just play with an idea, put it on JSFiddle, Codepen, JSBin, Dabblet, or whatever other amazing tool we have right now and share it.

Don’t build a perfect plugin, don’t build a solution dependent on preprocessors and libraries. Go vanilla and just play with what we have in browsers today. CSS Animations and Transitions, Canvas, Audio and Video, HTML5 and Friends – we have so many cool toys to play with. Don’t explore the main use case either. Yes, Canvas is for putting things on the screen, but it is also about reading image data.

We got were we are by playing with things. Never forget this and never stop playing.

View full post on Christian Heilmann

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.22_1171]
Rating: 0 (from 0 votes)