Scrolling effects have been used on the web for a long time, and although there are already many plug-ins from which to choose, only a small part of them has such a small weight and simplicity that are required for many designers and developers. Most of the plugins I've seen are trying to do too much, making them difficult to include in their projects.
Not so long ago, Apple introduced the iPhone 5S, and the
site with the presentation , where the page was divided into sections, and each section described one of the features of the product. I thought it was a great way to present a product, eliminating the possibility of missing key information.
I went in search of a suitable plug-in, and surprisingly, I did not find one. The page scroll plugin was born.
Paginated 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 the idea to planning, testing and uploading free code.
Note: before building a plugin, I was already aware of disputes as to whether scripts should change the natural behavior of browsers with regard to scrolling - this can confuse users. Therefore, I tried to reduce the negative effect of changes in habitual behavior. In the settings of the plugin, you can set the size of the screens in which the plugin returns to normal screen scrolling. Thus, on low-power devices such as smartphones and tablets, you can save the speed of the site. In addition, you can set the duration of the animation at the transition between sections.
Why all this?
As I have already mentioned, the majority of ready-made plugins 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 well
1. Drawings
I started planning a plugin from general to specific. He should scroll through the sections. To do this, disable the normal scrolling in the browser, while feeding sections one by one, and promoting the page if necessary.
You can imagine everything in your mind, and you can make sketches.Divide the concept into small tasks, solving each in sequence.
1. Prepare the layout sections
Disable normal scrolling by applying overflow: hidden to the body. Arrange the sections in the desired sequence, calculate and adjust the necessary information and classes.
2. Install the manual scrolling trigger
The trigger is caught through jQuery, we define the scrolling direction, we move the layout using CSS.
3. Adding opportunities
Add adaptability, loop, scrolling support on touchscreens, pagination, etc.
4. Check in different browsers.
Check the browsers Chrome, Safari, Firefox, Internet Explorer 10 and the most popular OSes 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. Expand support.
Let us explore other ways to increase support for the plugin.
2. Build the foundation
Having designed the plugin, I started building the basis on this template:
!function($) { var defaults = { sectionContainer: "section", … }; $.fn.onepage_scroll = function(options) { var settings = $.extend({}, defaults, options); … } }($)
We start the template with the! Function ($) {...} ($) module, which places the jQuery global variable in the local area - this will help reduce the load and prevent conflicts with other libraries.
The defaults variable contains default settings.
$ .fn.onepage_scroll is the main function that initializes everything. If you make your plugin, do not forget to write another 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 of the name of the plug-in, and then through the dash - the name for the style, for example: .onepage-wrapper.
The foundation is laid, let's proceed to the first function.
3. Prepare the layout and arrange the sections
At first I went the wrong way. I thought I would arrange all the sections in order, passing them in a loop. What I got 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 goes through all the selectors (sectionContainer is defined in the variable section by default), assigns position: absolute and assigns the next top the correct top position so that they do not overlap each other.
The position at the top (top) is stored in topPos. We start from scratch and add with each cycle. To make each section occupy the entire page, I set their height to 100% and add to topPos 100.
The 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 might think that the next step is to move each section to a new position, when the scroll trigger works ... But there is a better way. Instead of shifting each of the sections in the 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 sections so that they are precisely positioned in the window, without recounting everything anew. In addition, it facilitates control over 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 desired direction.
function init_scroll(event, delta) { var deltaOfInterest = delta, timeNow = new Date().getTime(), quietPeriod = 500;
At first we hook the function on the mousewheel event (DOMMouseScroll in Firefox), then we can intercept the data and determine the direction. We build in handling init_scroll, which gets a wheelData for this.
In an ideal world, it would be enough to count the change in wheelData. However, when animating sequences, you need 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, since every browser handles it differently. For example, in Chrome and Firefox, setInterval slows down in inactive tabs, as a result, functions do not work in time. As a result, I stopped using the function that returns the current time.
var timeNow = new Date().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, then to check if the animation took more than 500 ms. If not occupied, then the transformation does not occur and the animation does not overlap. Work with the current time is more reliable, 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 invokes the final transformation method to move the next section into 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 specifying individual styles so that developers could change the settings in the plugin itself (basically, the speed and acceleration of the animation), and I didn’t have to search through the style file for settings. In addition, the percentage of transformation still needs to be recalculated, so you can't 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 have released version 1.2.1, which adds a lot of callbacks and loops, and the most difficult part is adaptability.
I did not do the plugin initially with the expectation of mobile platforms (which I regret). Instead, we had to track and recalculate touchscreen events to the appropriate form for use in init_scroll. This does not work well in all browsers, so I had to build in the ability to roll back - when the browser returns to normal scrolling when a certain window width is reached.
var defaults = { responsiveFallback: false … }; function responsive() { 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 variable by default. Use responsiveFallback to determine when the plugin should roll back. This code defines 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 you to scroll as usual. If the width exceeds the value, the plugin checks for the presence of the class disabled-onepage-scroll to see if it is initialized. If not, it is initialized again.
The solution is not perfect, but it gives developers and designers the opportunity to choose how to display their website on a mobile platform, instead of completely rejecting these platforms.
6. Testing in different browsers.
Testing is an important part of development. Before releasing a plugin, you need to make sure that it works on most machines. I always develop in Chrome - first, I like its developer tools, and second, I know that if the plugin works in Chrome, it will most likely work in Safari and Opera.
Usually I use Macbook Air for development, and at home I have a PC for testing. After the plugin works in Chrome, I check it manually in Safari, Opera, and at the end - in Firefox on Mac OS X, and then on Chrome, Firefox and Internet Explorer 10 on Windows.
This is not all possible browsers, but open source is good because other developers will be able to test and correct the errors themselves - that is its meaning. You can not immediately make the perfect product, and set the springboard for the start.
Do not forget to test your plugins on mobile devices.To facilitate testing, after the end of the plug-in, I create a demo page to show all its features, and upload to my site. There are errors that can not be caught locally, but which come out when working on a real site. When the demo page works, I start testing on mobile devices.
7. We lay out the plugin in open source.
The last step is sharing the plugin on github. To do this, you need to 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 there a plug-in and we configure structure.
Repository structureCustomize as you like. I do this:
- the demo directory contains working demos, with all the necessary resources
- the usual and compressed version of the plug-in lies at the root
- CSS and test resources, such as images (if necessary) are at the root
- readme file in root
Readme structureAn important stage - writing clear instructions for the open-source community. Usually I write them in the readme, but for complex cases you may need a wiki page. As I write the readme:
1. Introduction
I explain the purpose of the plugin, give an image and a link to the demo.
2. Requirements and compatibility.
It is better to bring this section higher, so that it is immediately clear whether the person will be able to use the plugin.
3. Basic principles of use
Step-by-step instructions, starting from connecting jQuery, ending with HTML markup and calling a function. Also describes the settings.
4. Advanced use.
More complex instructions - public methods, callbacks and other useful information.
5. Other resources
Links to tutorial, thank you, etc.
8 Expanding support
In general, one could do without jQuery, but I was in a hurry to put it in the 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 a rule, and for Smashing Magazine's readers, I have to 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 Zepto versionPure JavaScript RepositoryZepto repositoryRecycle plugin in pure javascriptSuch processing may seem like a hard task, but this is only from the beginning. The most difficult thing is not to be mistaken with math. Since I already did it, the development of auxiliary functions to get rid of jQuery took me only a few hours.
The plugin is based on CSS3, so you just had to replace the jQuery calls with similar ones. At the same time I reorganized the structure of the script:
- default variable values
All the same as in the previous version.
- 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 plug-in methods are scroll events, page transformation, adaptive rollback and scrolling tracking.
- public methods
All methods for developers: moveDown (), moveUp () and moveTo ()
- helper methods
Everything that overrides jQuery calls.
There are a couple of unpleasant moments - a separate function only to add or remove the name of the style, or use document.querySelector instead of $. But in the end we got a better structured plugin.
Rebuilding plugin for ZeptoI decided to support Zepto, despite the fact that it is designed only for the most modern browsers (IE10 +), since it works faster and more efficiently than jQuery 2.0+, while it has a more flexible API. Zpeto is 4 times less jQuery, which affects the page loading speed. Due to the fact that people more often use smartphones, Zepto becomes a better alternative.
Modifying 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 support for CSS3 animation 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 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 animate without defining all styles or setting up callbacks on your own.
And why bother with this?
As more people use jQuery, it becomes more complex and sometimes slow. If you support other frameworks, your plugin will be more popular.
Redesign from the start will also help you make better plugins in the future. jQuery and other libraries forgive minor errors, such as missing commas - as a result, you don’t really care about the quality of your work. Without these indulgences on pure JavaScript, I felt better how my plug-in worked — how it works, what affects performance, and what can be improved.
Although libraries like jQuery have made life easier for us, using them is not the most effective way to achieve a goal. Some plugins can do without it.
Conclusion
Well, here's the whole process of creating the “One Page Scroll” plugin. There were mistakes, but I learned from them in the course of development. If I developed it today, I would concentrate on mobile devices, and add more comments to the code.
Without the support of communities such as GitHub, StackOverflow, and Smashing Magazine, I would not be able to make the plugin so quickly. These communities greatly helped me in my work, that's why I make my plugins available for free to everyone. This is my way to pay for great support.
ResourcesDemodownload plugin