How I Created the Open Source One Page Scroll Plugin

Original author: Pete Rojwongsuriya
  • Transfer
Scrolling effects have been used on the web for a long time, and although there are already many plugins to choose from, only a small part of them are as light and simple as many designers and developers need. Most of the plugins I have seen try to do too much, making it difficult to integrate them into your projects.

Not so long ago, Apple introduced the iPhone 5S, and a presentation site where the page was divided into sections, and each section described one of the features of the product. I thought this was a great way to present the product, eliminating the possibility of missing key information.

I went in search of a suitable plugin, and to my surprise, I did not find one. So the page scrolling plugin was born.

Pagination scroll plugin.



A jQuery-based plugin that allows you to create a layout for a page with several sections with minimal use of markup.

I will tell you how it was created, from idea to planning, testing and laying out free code.

Note: before building the plugin, I was already aware of disputes as to whether scripts should change the browsers' natural behavior with respect to scrolling - this can confuse users. Therefore, I tried to reduce the negative effect of changes in habitual behavior. In the plugin settings, you can set the screen sizes at which the plugin returns to the normal scrolling of the screen. Thus, on low-power devices such as smartphones and tablets, you can save site performance. In addition, you can set the duration of the animation when moving between sections.

Why all this?



As I already mentioned, most of the ready-made plug-ins included many optional functions, which made integration difficult. This plugin should be:

- easy to use
- easy to integrate
- require minimal markup
- perform one function, but good

1. Drawings


I started planning the plugin from general to private. It should scroll through sections. To do this, disable normal scrolling in the browser, while feeding sections one by one, and promoting the page if necessary.


You can imagine everything in the mind, but you can make sketches.

We divide the concept into small tasks, solving each one sequentially.

1. Prepare the layout of the sections
Turn off normal scrolling by applying overflow: hidden to the body. We arrange the sections in the desired sequence, calculate and adapt the necessary information and classes.

2. Set the manual scrolling
trigger. Catch the trigger through jQuery, determine the direction of scrolling, move the layout using CSS.

3. Add features
Add adaptability, a loop, support for scrolling on touchscreens, pagination, etc.

4. Check in different browsers.
Let's check the browsers Chrome, Safari, Firefox, Internet Explorer 10 and the most popular operating systems Windows, Mac OS X, iOS and Android 4.0+.

5. Make the plugin available in the repository.
Create a repository, write instructions for using the plugin

. 6. Extend support.
We’ll explore other ways to increase plugin support.

2. Building the foundation


Having designed the plugin, I started building the foundation on this template:

!function($) {
   var defaults = {
      sectionContainer: "section",
      …
   };
   $.fn.onepage_scroll = function(options) {
      var settings = $.extend({}, defaults, options);
      …
   }
}($)


We start the template with the module! Function ($) {...} ($), which puts the jQuery global variable in the local area - this will help reduce the load and prevent conflicts with other libraries.

The defaults variable contains the default settings.

$ .fn.onepage_scroll is the main function that initializes everything. If you are creating your own plugin, remember to write a different name instead of onepage_scroll.

You can disable standard scrolling by assigning the overflow: hidden property to the body tag
through the class name specific to this plugin. It is important to use unique style names to avoid conflict with existing ones. I usually use the abbreviation from the plugin name, and then through the dash - the name for the style, for example: .onepage-wrapper.

The foundation is laid, proceed to the first function.

3. Prepare the layout and arrange the sections


At first I went the wrong way. I thought that I would arrange all the sections in order, passing them in a loop. What I did first:

var sections = $(settings.sectionContainer);
var topPos = 0;
$.each(sections, function(i) {
   $(this).css({
      position: "absolute",
      top: topPos + "%"
   }).addClass("ops-section").attr("data-index", i+1);
   topPos = topPos + 100;
});


The loop iterates over all the selectors (sectionContainer is defined in the variable section by default), assigns position: absolute and assigns the next top section the correct top position so that they do not run into each other.

The position above (top) is stored in topPos. We start from scratch and add with each cycle. So that each section occupies the entire page, I set their height to 100% and add to topPos 100.

Development and verification took me a couple of hours, but in the next step I realized that all this was not necessary.

4. Manual trigger and page conversion


One would think that the next step is to move each section to a new position when the scroll trigger fires ... But there is a better way. Instead of shifting each of the sections in a loop, I just put them all in one container and use the translate3d function from CSS3 to shift it. This function supports percentages, we can move the sections so that they are accurately positioned in the window, without recounting everything again. In addition, it facilitates control over the speed and other animation parameters.


The first solution is not always the most effective, so do not forget to leave time for experiments.

Now it remains only to determine the direction of scrolling and move the container in the right direction.

functioninit_scroll(event, delta) {
   var deltaOfInterest = delta,
   timeNow = newDate().getTime(),
   quietPeriod = 500;
   // Cancel scroll if currently animating or within quiet periodif(timeNow - lastAnimation < quietPeriod + settings.animationTime) {
      event.preventDefault();
      return;
   }
   if (deltaOfInterest < 0) {
      el.moveDown()
   } else {
      el.moveUp()
   }
   lastAnimation = timeNow;
}
$(document).bind('mousewheel DOMMouseScroll', function(event) {
   event.preventDefault();
   var delta = event.originalEvent.wheelDelta || -event.originalEvent.detail;
   init_scroll(event, delta);
});


First, we hook the function to the mousewheel event (DOMMouseScroll in Firefox), then it will be possible to intercept the data and determine the direction. We embed init_scroll in the processing, which receives wheelData for this.

In an ideal world, it would be enough to count the change to wheelData. However, when animating sequences, it is necessary to embed a check so that the trigger event is not duplicated (otherwise the image will overlap during animation). You can use setInterval to call each animation in turn, but this will not provide accuracy and reliability, as each browser handles it in its own way. For example, in Chrome and Firefox, setInterval slows down in inactive tabs, as a result, functions do not work out on time. As a result, I settled on using a function that returns the current time.

var timeNow = newDate().getTime(),
quietPeriod = 500;
…
if(timeNow - lastAnimation < quietPeriod + settings.animationTime) {
   event.preventDefault();
   return;
}
…
lastAnimation = timeNow;


In this fragment, which I quoted from the previous code, I save the current time in timeNow, so that later I can check if the animation took more than 500 ms. If it is not busy, then the transformation does not occur and the animation does not overlap. It’s more reliable to work with the current time, because it is the same for everyone.

if (deltaOfInterest < 0) {
   el.moveDown()
} else {
   el.moveUp()
}


The moveUp and moveDown functions change the layout attributes to reflect the current state of the site. Each of them at the end of the work calls the final transformation method in order to move the next section to the viewport.

$.fn.transformPage = function(settings, pos, index) {
   …
   $(this).css({
      "-webkit-transform": ( settings.direction == 'horizontal' ) ? "translate3d(" + pos + "%, 0, 0)" : "translate3d(0, " + pos + "%, 0)",
      "-webkit-transition": "all " + settings.animationTime + "ms " + settings.easing,
      "-moz-transform": ( settings.direction == 'horizontal' ) ? "translate3d(" + pos + "%, 0, 0)" : "translate3d(0, " + pos + "%, 0)",
      "-moz-transition": "all " + settings.animationTime + "ms " + settings.easing,
      "-ms-transform": ( settings.direction == 'horizontal' ) ? "translate3d(" + pos + "%, 0, 0)" : "translate3d(0, " + pos + "%, 0)",
      "-ms-transition": "all " + settings.animationTime + "ms " + settings.easing,
      "transform": ( settings.direction == 'horizontal' ) ? "translate3d(" + pos + "%, 0, 0)" : "translate3d(0, " + pos + "%, 0)",
      "transition": "all " + settings.animationTime + "ms " + settings.easing
   });
   …
}


This is a transformation method that shifts sections. I did them in javascript instead of setting individual styles so that developers had the opportunity to change the settings in the plug-in itself (basically, the speed and speed of the animation), and there was no need to rummage through the stylesheet in search of settings. In addition, the percentage of transformation still needs to be recounted, so you can not do without javascript.

5. Additional features


At first I didn’t want to add anything, but I received so many reviews from the GitHub community that I decided to gradually improve the plugin. I released version 1.2.1, which adds a lot of callbacks and loops, and the most difficult thing is adaptability.

I did not initially do the plugin with the expectation of mobile platforms (which I regret). Instead, I had to track and recount touchscreen events in a form suitable for use in init_scroll. Not all browsers work well, so I had to build in the rollback feature - when the browser returns normal scrolling when it reaches a certain window width.

var defaults = {
   responsiveFallback: false
   …
};
functionresponsive() {
   if ($(window).width() < settings.responsiveFallback) {
      $("body").addClass("disabled-onepage-scroll");
      $(document).unbind('mousewheel DOMMouseScroll');
      el.swipeEvents().unbind("swipeDown swipeUp");
   } else {
      if($("body").hasClass("disabled-onepage-scroll")) {
         $("body").removeClass("disabled-onepage-scroll");
         $("html, body, .wrapper").animate({ scrollTop: 0 }, "fast");
      }
      el.swipeEvents().bind("swipeDown",  function(event) {
         if (!$("body").hasClass("disabled-onepage-scroll")) event.preventDefault();
         el.moveUp();
      }).bind("swipeUp", function(event){
         if (!$("body").hasClass("disabled-onepage-scroll")) event.preventDefault();
         el.moveDown();
      });
      $(document).bind('mousewheel DOMMouseScroll', function(event) {
         event.preventDefault();
         var delta = event.originalEvent.wheelDelta || -event.originalEvent.detail;
         init_scroll(event, delta);
      });
   }
}


Define the default variable. We use responsiveFallback to determine when the plugin should roll back. This code determines the width of the browser. If the width is less than the value from responsiveFallback, the function removes all events, brings the page to the beginning and allows it to scroll as usual. If the width exceeds the value, the plugin checks for the disabled-onepage-scroll class to see if it is initialized. If not, it is reinitialized.

The solution is not perfect, but it allows developers and designers to choose how to display their site on a mobile platform, instead of completely rejecting these platforms.

6. Testing in different browsers.


Testing is an important part of development, before releasing the plugin you need to make sure that it works on most machines. I always develop in Chrome - firstly, I like its developer tools, secondly I know that if the plugin works in Chrome, most likely it will work in Safari and Opera.

I usually use a Macbook Air for development, and at home I have a PC for verification. After the plugin works in Chrome, I check it manually in Safari, Opera, and in the end - in Firefox on Mac OS X, and then Chrome, Firefox and Internet Explorer 10 on Windows.

These are not all possible browsers, but the open source is good because other developers will be able to test and fix errors themselves - this is its meaning. You can not immediately make the perfect product, but set a springboard for the start.


Remember to test your plugins on mobile devices.

To facilitate testing, after the plugin is completed I create a demo page to show all its features and upload it to my site. There are errors that cannot be caught locally, but which crawl out when working on a real site. When the demo page works, I start testing on mobile devices.

7. We post the plugin in open source


The last step is to share the plugin on GitHub. To do this, create an account there, configure Git and create a new repository. Then clone it to the local machine - this will create a directory with the name of the plugin. We copy the plugin there and configure the structure.

Repository structure

Customize as you wish. I do this:

- the demo directory contains working demos with all the necessary resources
- the regular and compressed version of the plugin are at the root
- CSS and test resources, such as pictures (if necessary) are at the root
- readme file at the root

readme structure The

important step is to write clear instructions for the open source community. I usually write them in readme, but for complex cases a wiki page may be needed. How do I write readme:

1. Introduction I
explain the purpose of the plugin, give an image and a link to the demo.
2. Requirements and compatibility.
It’s better to take this section higher so that it is immediately clear whether a person can use the plugin.
3. Basic principles of use
Step-by-step instructions, starting from jQuery connection, ending with HTML markup and function call. The settings are also described.
4. Advanced use.
More complex instructions are public methods, callbacks, and other useful information.
5. Other resources
Links to training, thanks, etc.

8 Expanding Support


In general, one could do without jQuery, but I was in a hurry to put it in open source, so I decided to reduce development time and rely on ready-made functions in jQuery.

But to clear my conscience, I reworked the plugin in pure javascript (a version with Zepto support is also available). On pure JS there is no need to include jQuery, everything works out of the box.

To make amendments, and exclusively for Smashing Magazine's readers, I have rebuilt One Page Scroll using pure JavaScript (a Zepto version is also available). With the pure JavaScript version, you no longer need to include jQuery. The plugin works right out of the box.

Pure JS and version for Zepto

Pure JavaScript Repository
Zepto repository

Recycling the plugin in pure JavaScript

Such processing may seem difficult, but it is only at first. The hardest part is not to make a mistake with math. Because I already did this, it took me a few hours to develop helper functions to get rid of jQuery.

The plugin is based on CSS3, so you just had to replace jQuery calls with similar ones of your own. At the same time, I reorganized the structure of the script:

- the default values ​​of variables
Everything is the same as in the previous version
- the initialization function
Prepares and arranges the layout and initialization of what happens when the onePageScroll function is called. Here are all the procedures that assign class names, attributes, and positioning styles.
- private methods
All internal methods of the plugin are scroll events, page transformation, adaptive rollback and scrolling tracking.
- public methods
All methods for developers: moveDown (), moveUp () and moveTo ()
- helper methods
Anything that overrides jQuery calls.

There were a couple of unpleasant moments - a separate function only to add or remove a style name, or use document.querySelector instead of $. But in the end we got a better structured plugin.

Rebuild the plugin for Zepto

I decided to support Zepto, despite the fact that it is designed only for the most modern browsers (IE10 +), because It works faster and more efficiently than jQuery 2.0+, while it has a more flexible API. Zpeto is 4 times less than jQuery, which affects page loading speed. Due to the fact that people use smartphones more often, Zepto is becoming a better alternative.

Converting a plugin from jQuery to Zepto is easier because they have similar APIs. Almost everything is the same except for the animation part. Since the Zepto $ .fn.animate () function has CSS3 animation support and animationEnd callback support, the following part:

$(this).css({
   "-webkit-transform": "translate3d(0, " + pos + "%, 0)",
   "-webkit-transition": "-webkit-transform " + settings.animationTime + "ms " + settings.easing,
   "-moz-transform": "translate3d(0, " + pos + "%, 0)",
   "-moz-transition": "-moz-transform " + settings.animationTime + "ms " + settings.easing,
   "-ms-transform": "translate3d(0, " + pos + "%, 0)",
   "-ms-transition": "-ms-transform " + settings.animationTime + "ms " + settings.easing,
   "transform": "translate3d(0, " + pos + "%, 0)",
   "transition": "transform " + settings.animationTime + "ms " + settings.easing
});
$(this).one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(e) {
   if (typeof settings.afterMove == 'function') settings.afterMove(index, next_el);
});


You can replace it with this code:

$(this).animate({
      translate3d: "0, " + pos + "%, 0"
   }, settings.animationTime, settings.easing, function() {
      if (typeof settings.afterMove == 'function') settings.afterMove(index, next_el);
   });
}


Zepto allows you to make animations without defining all styles or self-assigning callbacks.

And why bother?



As more and more people use jQuery, it becomes more complex and sometimes slows down. If you make support for other frameworks, your plugin will be more popular.
Remaking from the start will also help you make plugins better in the future. jQuery and other libraries forgive small errors, such as missing commas - as a result, you do not really care about the quality of your work. Without these concessions in pure JavaScript, I felt better how my plugin worked - how it works, what affects performance, and what can be improved.

Although libraries like jQuery made life easier for us, using them is not the most efficient way to achieve our goal. Some plugins can do this too.

Conclusion



Well, here you have the whole process of creating the plugin “One Page Scroll”. There were errors, but I learned from them during development. If I developed it today, I would concentrate on mobile devices and add more comments to the code.

Without the support of communities like GitHub, StackOverflow, and Smashing Magazine, I would not be able to make the plugin so fast. These communities helped me a lot in my work, that's why I make my plugins available free of charge for everyone. This is my way to pay for great support.

Resources

Demo

Download Plugin

Also popular now: