Introducing Jelly Navigation Menu: When Canvas Meets PaperJS

Advertisement

It’s our great pleasure to support active members of the Web design and development community. Today, we’re proud to present the Jelly Navigation Menu that shows the power of PaperJS and TweenJS when used together. This article is yet another golden nugget of our series of various tools, libraries and techniques that we’ve published here on Smashing Magazine: LiveStyle1, PrefixFree2, Foundation3, Sisyphus.js4, GuideGuide5, Gridpak6, JS Bin7 and CSSComb8. — Ed.

There is no doubt that the Web helps designers and developers find the best inspiration and resources for their projects. Even though there are a bunch of different tutorials and tips available online, I feel that HTML5 canvas techniques are missing the most. Good news: I had the chance to fulfill this wide gap. In this article, I would like to share my experience and story of how I brought the “Jelly Navigation Menu” to life. Credits go to Capptivate.co9 and Ashleigh Brennan’s10 icons — they were my inspiration for this project.

Before We Start

The source code for this project was originally written in CoffeeScript — I believe it’s a better way to express and share JavaScript code that way. I will refer to CoffeScript’s source in code sections within this post and you’ll also notice links to CodePens that have been rewritten in JavaScript and represent local parts of code as well. I recommend downloading the source code on GitHub11 so you can easily follow me while I explain the necessary code in detail.

I used PaperJS12 for the canvas graphics and TweenJS13 for the animations. Both of them tend to freak out some folks, but don’t worry, they are really intuitive and easy to understand. If you’d like to learn how to set up PaperJS and TweenJS environments, you can fork this cool bootstrap pen14 for online fun or this git repo15 if you want to experiment locally.

16
A preview of the Jelly Navigation Menu17.

See Full Demo18

First Step: Changing The Section Shape

Our first aim is to change the menu section shape by manipulating the curves. Every object is made up of anchor points19. These points are connected with each other by curves. So each point has “In” and “Out” handles to define the location and direction of specific curves. Folks who work with vector editors should feel comfortable with this step.

20
In Paper.js, paths are represented by a sequence of segments that are connected by curves. A segment consists of a point and two handles, defining the location and direction of the curves. See the handles in action21.

All we need to do is to change the handleOut position of the top-left and bottom-right points. To achieve this, I wrote simple so-called “toppie” and “bottie” functions:

toppie:(amount)->
  @base.segments[1].handleOut.y = amount
  @base.segments[1].handleOut.x = (@wh/2)

bottie:(amount)->
  @base.segments[3].handleOut.y = amount
  @base.segments[3].handleOut.x = - @wh/2

# @wh/2 is section center.
# @base variable holds section's rectangle path.

It’s important to set the handle’s X position to exactly the middle of the section, so that the curve will turn out to be symmetrical.

See Demo #122

Second Step: Calculating The Scrolling Speed

So the next thing that needs to be done is to calculate the scrolling speed and direction, and then pass this data to the bottie and toppie functions. We can listen to the container’s scrolling event and determine the current scrolling position (in my case the “container” is a #wrapper element whereas it is a window object in the pen examples).

# get current scroll value
window.PaperSections.next = window.PaperSections.$container.scrollTop()

# and calculate the difference with previous scroll position
window.PaperSections.scrollSpeed = (window.PaperSections.next - window.PaperSections.prev)

# to make it all work, all we have left to do is to save current scroll position to prev variable
window.PaperSections.prev = window.PaperSections.next

This is repeated for every scrolling event. In this code snippet, window.PaperSections is just a global variable. I also made a few minor additions in my implementation:

  • A silly coefficient to increase scroll speed by multiplying it by 1.2 (just played around with it),
  • I sliced the scroll speed result by its maximum so that it is not larger than sectionHeight/2,
  • I also added a direction coefficient (it could be 1 or -1, you can change it in dat.gui on the top right of the page). This way you can control the reaction direction of sections.

Here is the final code:

if window.PaperSections.i % 4 is 0
  direction = if window.PaperSections.invertScroll then -1 else 1
  window.PaperSections.next = window.PaperSections.$container.scrollTop()
  window.PaperSections.scrollSpeed = direction*window.PaperSections.slice 1.2*(window.PaperSections.next - window.PaperSections.prev), window.PaperSections.data.sectionheight/2
  window.PaperSections.prev = window.PaperSections.next
  window.PaperSections.i++

In this example, if window.PaperSections.i % 4 is 0 helps us react on every fourth scroll event — similar to a filter. That function lives in window.PaperSections.scrollControl.

That’s it! We’re almost done! It couldn’t be any easier, right? Try out the scrolling here23.

See Demo #224

Step Three: Make It Jelly!

In this final step, we need to animate the toppie and bottie functions to 0 with TweenJS’ elastic easing everytime the scrolling stops.

3.1 Determine When Scrolling Stops

To do this, let’s add the setTimeout function to our window.PaperSections.scrollControl function (or scroll) with 50ms delay. Each time when the scrolling event fires up, the Timeout is cleared except for the last one, i.e. once scrolling stops, the code in our timeout will execute.

clearTimeout window.PaperSections.timeOut
window.PaperSections.timeOut = setTimeout ->
  window.PaperSections.$container.trigger 'stopScroll'
  window.PaperSections.i = 0
  window.PaperSections.prev = window.PaperSections.$container.scrollTop()
    , 50

The main focus here is the window.PaperSections.$container.trigger stopScroll event. We can subscribe to it and launch the animation appropriately. The other two lines of code are simply being used to reset helper variables for later scrollSpeed calculations.

See Demo #325

3.2 Animate Point’s handleOut To “0”

Next, we’ll write the translatePointY function to bring our jelly animation to life. This function will take the object as a parameter with the following key-value sets:

{
  // point to move (our handleOut point)
  point: @base.segments[1].handleOut,

  // destination point
  to: 0,

  // duration of animation
  duration: duration
}

The function body is made up of the following:

translatePointY:(o)->
  # create new tween(from point position) to (options.to position, with duration)
  mTW = new TWEEN.Tween(new Point(o.point)).to(new Point(o.to), o.duration)

  # set easing to Elastic Out
  mTW.easing TWEEN.Easing.Elastic.Out

  # on each update set point's Y to current animation point
  mTW.onUpdate ->
    o.point.y = @y

  # finally start the tween
  mTW.start()

The TWEEN.update() function also has to be added to every frame of the PaperJS animation loop:

onFrame = ->
  TWEEN.update()

Also, we need to stop all animations on scrolling. I added the following line to the scroll listener function:

TWEEN.removeAll()

Finally, we need to subscribe to the stopScroll event and launch the animations by calling our translatePointY function:

window.PaperSections.$container.on 'stopScroll', =>
  # calculate animation duration
  duration = window.PaperSections.slice(Math.abs(window.PaperSections.scrollSpeed*25), 1400) or 3000

  # launch animation for top left point
  @translatePointY(
    point:      @base.segments[1].handleOut
    to:           0
    duration: duration
  ).then =>
    # clear scroll speed variable after animation has finished
    # without it section will jump a little when new scroll event fires
    window.PaperSections.scrollSpeed = 0

  # launch animation for bottom right point
  @translatePointY
    point:      @base.segments[3].handleOut
    to:           0
    duration: duration

Et voilà!

See Demo #426

Note: In my source code27 of the translatePointY function, I added a deferred object for chaining, optional easing and onUpdate function. It is omitted here for the sake of simplicity.

In Conclusion

Last but not least, a class for the sections has to be added. Of course, you can make as many instances of it as you like; you just need to define initial Y offset and colors. Also, you will need to make sure that the section in your layout has the same height as the section in canvas. Then we can just apply translated3d to both on the scroll event and animations. This will cause HTML sections to move properly, just like the canvas sections, and hence produce a realistic animation.

HTML Sections28

The reason why we need to use translate3d instead of translateY is to make sure that Webkit rendering engines use hardware acceleration while rendering them, so we do not drop out of the 60fps animation budget. But keep your eyes wide open if your sections contain any text. 3D transforms will drop anti-aliasing from subpixel to grayscale, so it may look a bit blurry!

Feedback

I look forward to your thoughts, questions and/or your feedback to the Jelly Navigation Menu29 in the comments section below. You can also reach out to me via Twitter30 anytime!

(vf) (il)

Footnotes

  1. 1 http://www.smashingmagazine.com/2013/08/08/release-livestyle-css-live-reload/
  2. 2 http://www.smashingmagazine.com/2011/10/12/prefixfree-break-free-from-css-prefix-hell/
  3. 3 http://www.smashingmagazine.com/2011/10/25/rapid-prototyping-for-any-device-with-foundation/
  4. 4 http://www.smashingmagazine.com/2011/12/05/sisyphus-js-client-side-drafts-and-more/
  5. 5 http://www.smashingmagazine.com/2012/01/03/guideguide-free-plugin-for-dealing-with-grids-in-photoshop/
  6. 6 http://www.smashingmagazine.com/2012/03/19/gridpak-the-responsive-grid-generator/
  7. 7 www.smashingmagazine.com/2012/07/23/js-bin-built-for-sharing-education-and-real-time/
  8. 8 http://www.smashingmagazine.com/2012/10/02/csscomb-tool-sort-css-properties/
  9. 9 http://capptivate.co/2013/07/12/making-3/
  10. 10 http://dribbble.com/ash-brennan
  11. 11 https://github.com/sol0mka/paperjs-scroll-sections
  12. 12 http://paperjs.org/
  13. 13 https://github.com/sole/tween.js/
  14. 14 http://codepen.io/sol0mka/pen/Ivisr
  15. 15 https://github.com/sol0mka/paperjs-scroll-sections
  16. 16 http://codepen.io/sol0mka/pen/Jsyxq
  17. 17 http://codepen.io/sol0mka/pen/Jsyxq
  18. 18 http://s.codepen.io/sol0mka/fulldetails/Jsyxq
  19. 19 http://www.youtube.com/watch?v=5BPrGoJZ8WI
  20. 20 http://www.smashingmagazine.com/wp-content/uploads/2013/08/handle-out.png
  21. 21 http://paperjs.org/tutorials/paths/working-with-path-items/
  22. 22 http://s.codepen.io/sol0mka/fulldetails/cjadz
  23. 23 http://codepen.io/sol0mka/pen/zIBGr
  24. 24 http://s.codepen.io/sol0mka/fulldetails/zIBGr
  25. 25 http://s.codepen.io/sol0mka/fulldetails/JfDvK
  26. 26 http://s.codepen.io/sol0mka/fulldetails/qiwFe
  27. 27 https://github.com/sol0mka/paperjs-scroll-sections
  28. 28 http://codepen.io/sol0mka/full/Jsyxq
  29. 29 http://codepen.io/sol0mka/full/Jsyxq
  30. 30 https://twitter.com/mailtolego

↑ Back to topShare on Twitter

Oleg Solomka, aka LegoMushroom, born in Donetsk, Ukraina, studied Computer Science and Technology and helplessly fell in love with the World Wide Web ever since. He occasionally writes for Habra Habr and works as a front-end developer at Tracks Flow and is known for his front-end passion in regards to layout, coding and graphics. Oleg's hobbies include biking, dancing, travelling, listening to music and, last but not least, eating yummy fruits.

Advertising

Note: Our rating-system has caused errors, so it's disabled at the moment. It will be back the moment the problem has been resolved. We're very sorry. Happy Holidays!

  1. 1

    About time we started to see some exploitation of the canvas element for UI purposes. Great stuff!

  2. 2

    I’ve tried the demo on a Nexus 4 phone. The animation is terribly slow and actually breaks the navigation because of the stutter.

    I don’t really see how this improves the UI (even if i would ignore the slowness/stutter).

    • 3

      Yep, Marco, it is a desktop version so it could work after a fashion in mobiles. To make it work properly, you need handle scroll a litter bit differently. However for the greatest user experience you need to stick with a native app.

      • 4

        Android is also rather slow when it comes to rendering canvas animations, regardless of GPU acceleration*.

        *This may have changed with recent updates.

  3. 5

    This is some serieus sweet stuff.

  4. 6

    Abdullah Al-Haqbani

    August 15, 2013 4:10 pm

    Bravo!
    Thanks for sharing and spreading the knowledge :)

  5. 7

    One need’ a brain to do this.
    Absolutely Stunning.

  6. 8

    Doesn’t even remotely work in IE10 or IE11 Beta. Disappointing.

  7. 11

    Great Job! Thanks a lot!

  8. 12

    Thanks for the worthy tutorial.
    Special thanks to step by step demo.

  9. 13

    This is slick. It gives an aura of calming to the content, like if the canvas was a pond. Well done.

  10. 14

    Make It Jelly that really helps me thanks

  11. 15

    Thanx to all!

  12. 16

    This is great!

    It won’t work with the iOS native scroll though because it’s event blocking and the scroll events are a mess :/

  13. 17

    using ie11 beta. The example works.

  14. 18

    Pretty fly … we like.

  15. 19

    Great work Oleg! It looks incredibly nice!
    But it totally broke my Android 4.0 phone. :)

    Anyway thank you a lot! :)

  16. 20

    I can’t wait to try it on a website layout – I just had a cute idea as I saw this. I love it <3 thanks for sharing!

  17. 21

    Thanks it is awesome!

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top