├── .github
└── CODEOWNERS
├── .gitignore
├── LICENCE.txt
├── README.md
├── component.json
├── examples
├── galleryscrollers.html
├── horizontalpaged-strict.html
├── horizontalpaged.html
├── startendevent.html
├── verticalcontinuous.html
└── wholepage.html
├── lib
└── ftscroller.js
└── package.json
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Guessed from commit history
2 | * @Financial-Times/apps
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ~$*
2 | .DS_Store
3 | Thumbs.db
4 | Desktop.ini
--------------------------------------------------------------------------------
/LICENCE.txt:
--------------------------------------------------------------------------------
1 | Copyright (C) 2012-2014 The Financial Times Ltd.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FTScroller
2 |
3 | FTScroller is a library for adding momentum scrolling to web content on devices with a touch interface, compatible across most modern platforms including desktop browsers. Although recently support for `overflow: scroll` (or touch equivalents) has increased, this is often still not implemented in a cross-platform or backwards-compatible way, and with no support for features like snapping.
4 |
5 | FTScroller is developed by [FT Labs](http://labs.ft.com), part of the Financial Times. It is inspired by [Touchscroll](https://github.com/davidaurelio/TouchScroll) and [Zynga Scroller](https://github.com/zynga/scroller), but is a complete rewrite. It is extensively used in the [FT Web App](http://app.ft.com), and was developed to achieve better performance and compatibility, including mouse and touch input.
6 |
7 |
8 | ## Usage
9 |
10 | Include ftscroller.js in your JavaScript bundle or add it to your HTML page like this:
11 |
12 |
13 |
14 | The script must be loaded prior to instantiating a scroller on any element of the page.
15 |
16 | To create a scroller, with a few minimal options:
17 |
18 | ```js
19 | var containerElement, scroller;
20 |
21 | containerElement = document.getElementById('scrollcontainer');
22 |
23 | scroller = new FTScroller(containerElement, {
24 | scrollbars: false,
25 | scrollingX: false
26 | });
27 | ```
28 |
29 | ## Examples
30 |
31 | FTScroller is designed to accommodate a range of use cases. Here are some examples - feel free to copy the code and use as the basis for your own projects.
32 |
33 | * [Vertical continuous list](http://ftlabs.github.com/ftscroller/examples/verticalcontinuous.html)
34 | * [Horizontal paged](http://ftlabs.github.com/ftscroller/examples/horizontalpaged.html) (and a [strict version](http://ftlabs.github.com/ftscroller/examples/horizontalpaged-strict.html), constrained to scrolling one page at a time)
35 | * [Multiple vertical scrollers in a horizontally paged scroller](http://ftlabs.github.com/ftscroller/examples/galleryscrollers.html)
36 | * [Whole page scroller](http://ftlabs.github.com/ftscroller/examples/wholepage.html)
37 |
38 |
39 | ## Options
40 |
41 | Options must be specified at create-time by passing a JSON object as the second argument to the `FTScroller` constructor.
42 |
43 | * `alwaysScroll` Whether to always enable scrolling, even if the content of the scroller is not large enough to spill out of the container. This makes the scroller behave more like an element set to "overflow: scroll", with bouncing always occurring if enabled _(boolean, default false)_
44 | * `baseAlignments` Determines where to anchor the content when the scroller is set up, specified individually for each axis using a JSON object with the keys `x` and `y`. Valid alignments for each axis are -1 (top or left), 0 (center), and 1 (bottom or right). For example, the default baseAlignments of `{x:-1,y:-1}` will display the scroller initially scrolled to the top left of its range. This also affects the alignment of content where the content is smaller than the scroller.
45 | * `bouncing` Allow scroll bouncing and elasticity near the ends of the range and at snap points _(boolean, default true)_
46 | * `contentWidth` Define the scrollable width; if not defined, this will match the content width _(numeric, default undefined)_
47 | * `contentHeight` Define the scrollable height; if not defined, this will match the content height _(numeric, default undefined)_
48 | * `disabledInputMethods` Define any input methods to disable; on some multi-input devices custom behaviour may be desired for some scrollers. No inputs methods are disabled by default. _(object, default { mouse: false, touch: false, scroll: false, pointer: false, focus: false })_
49 | * `enableRequestAnimationFrameSupport` FTScroller will use requestAnimationFrame on platforms which support it, which is highly recommended; however this can result in the animation being a further half-frame behind the input method, increasing perceived lag slightly. To disable this, set this property to false. _(boolean, default true)_
50 | * `flinging` Allow a fast scroll to continue with momentum when released _(boolean, default true)_
51 | * `hwAccelerationClass` FTScroller uses normal translate properties rather than translate3d to position content when scrolling, and triggers hardware acceleration by adding CSS properties (specifically backface-visibility) to this class on platforms that support it. Adjusting this class allows for negotiating complex CSS inheritance to override the default behaviour of FTScroller if you want to change or disable backing layers/3D acceleration. _(string, default an internal class which triggers backing layers)_
52 | * `maxFlingDuration` Set the maximum time (ms) that a fling can take to complete once the input has ended _(numeric, default 1000ms)_
53 | * `scrollbars` Whether to display iOS-style scrollbars (which you can style yourself using `.ftscroller_scrollbar` and `.ftscroller_scrollbarx`/`.ftscroller_scrollbary`) while the content is animating _(boolean, default true)_
54 | * `scrollBoundary` The initial movement required to trigger a full scroll, in pixels; this is the point at which the scroll is exclusive to this particular FTScroller instance and flings become active _(integer, default 1)_
55 | * `scrollingClassName` The classname to add to the scroller container when it is being actively scrolled. This is disabled by default as it can cause a CSS relayout if enabled, but allows custom styling in response to scrolls _(string, default not set)_
56 | * `scrollResponseBoundary` The initial movement required to trigger a visual scroll update, in pixels _(integer, default 1)_
57 | * `scrollingX` Enable scrolling on the X axis if content is available _(boolean, default true)_
58 | * `scrollingY` Enable scrolling on the Y axis if content is available _(boolean, default true)_
59 | * `singlePageScrolls` _(was `paginatedSnap`)_ If snapping is enabled, restricts each scroll movement to one 'page' span. That is, if set to true, it will not be possible to move more than one page in a single movement. _(boolean, default false)_
60 | * `snapping` Enable snapping of content to defined 'pages' or segments _(boolean, default false)_
61 | * `snapSizeX` Define the horizontal interval content should snap to, in pixels. If this is not set, snapping will be based on pages corresponding to the container size. _(numeric, default undefined)_
62 | * `snapSizeY` Define the vertical interval content should snap to, in pixels. If this is not set, snapping will be based on pages corresponding to the container size. _(numeric, default undefined)_
63 | * `updateOnChanges` Automatically detect changes to the content of the scrollable element and update the scroller dimensions whenever the content changes. This is set to false automatically if `contentWidth` and `contentHeight` are specified _(boolean, default true)_
64 | * `updateOnWindowResize` Automatically catch changes to the window size and update the dimensions of the scroller. It's advisable to set this to true if the scroller has a flexible width or height based on the viewport size. _(boolean, default false)_
65 | * `windowScrollingActiveFlag` Whether to use a global property of the window object to control whether to allow scrolling to start or not. If the specified window property is set to a truthy value, the scroller will not react to input. If the property is not truthy, the scroller will set it to itself and will scroll. Where multiple scrollers exist on the same page, this ensures that only one can be used at a time, which is particularly useful for nested scrollers (see [Multiple vertical scrollers in a horizontally paged scroller](examples/galleryscrollers.html)). Note that FTScroller automatically allows only one scroller instance to be scrolled at once; use this flag to coordinate input with other parts of your code. _(string, default not set)_
66 | * `flingBezier` The bezier curve to use for momentum-like flings. _(CubicBezier, default CubicBezier(0.103, 0.389, 0.307, 0.966))_
67 | * `bounceDecelerationBezier` The bezier curve to use for deceleration when a fling hits the bounds. _(CubicBezier, default CubicBezier(0, 0.5, 0.5, 1))_
68 | * `bounceBezier` The bezier curve to use for bouncebacks when the scroll exceeds the bounds. _(CubicBezier, default CubicBezier(0.7, 0, 0.9, 0.6))_
69 | * `invertScrollWheel` If the scroller is constrained to an x axis, convert y scroll to allow single-axis scroll wheels to scroll constrained content. _(boolean, default true)_
70 |
71 | ## Public interface
72 |
73 | Once the scroller has been applied to an element, the return value from the constructor is an object that offers a number of public properties, methods and events.
74 |
75 | ### Properties
76 |
77 | * `scrollHeight` Gets the scrollable height of the contained content. **Read only**
78 | * `scrollLeft` Gets or sets the current left scroll offset of the scroller in pixels. When set, will cause the scroller to jump to that position (without animating)
79 | * `scrollTop` Gets or sets the current top scroll offset of the scroller in pixels. When set, will cause the scroller to jump to that position (without animating)
80 | * `scrollWidth` Gets the scrollable width of the contained content. **Read only**
81 | * `segmentCount` When snapping is enabled, returns the number of snap segments (which in many use cases can be considered 'pages'). **Read only**.
82 | * `currentSegment` Returns the index of the current segment, starting from 0. Updated when the scroller comes to rest on a new segment. Applies only when `snapping` is set to true. **Read only**.
83 | * `contentContainerNode` Returns the DOM node that contains the scroller contents, for use if you want to amend the contents. **Read only**.
84 |
85 | ### Methods
86 |
87 | * `addEventListener(eventname, callback)` Attaches a specified function to an FTScroller custom event. Available events are listed in the events section below. The function will be called by FTScroller when the event occurs.
88 | * `destroy(removeElements)` Unbinds all event listeners to prevent circular references preventing items from being deallocated, and clean up references to dom elements. Does not remove scroller markup from the DOM unless the optional `removeElements` argument is set to a truthy value.
89 | * `removeEventListener(eventname, callback)` Removes a previously bound function from an event. The function specified in the mathod must be the same object reference passed in the `addEventListener` call (not a redefinition of the same function code)
90 | * `scrollBy(horizontal, vertical[, duration])` Similar to `scrollTo` but allows scrolling relative to the current position rather than to an absolute offset.
91 | * `scrollTo(left, top[, duration])` Scroll to a specified left and top offet over a specified duration. If duration is zero, no animation will occur. If duration is `true` FTScroller will itself choose a duration based on the distance to be scrolled. The left and top inputs will be constrained by the size of the content and the snap points. If false is supplied for either left or top, that axis will not be scrolled and will retain its current offset.
92 | * `setSnapSize(width, height)` Configures the snapping boundaries within the scrolling element if snapping is active. If this is never called, segment size defaults to the width and height of the scroller, ie. page-at-a-time.
93 | * `updateDimensions(width, height[, nosnap])` Sets the dimensions of the scrollable content. If snapping is enabled, and you wish to disable updates of the snapping grid and prevent the current position from being updated, set `nosnap` to true; it defaults to false if not supplied.
94 | * `setDisabledInputMethods(disabledInputMethods)` Set the input methods to disable. No inputs methods are disabled by default. `(object, default { mouse: false, touch: false, scroll: false, pointer: false, focus: false })`
95 |
96 | ### Prototype methods
97 |
98 | * `getPrependedHTML([excludeXAxis, excludeYAxis, hwAccelerationClass])` - Provides half of the HTML that is used to construct the scroller DOM, for use to save a DOM manipulation on Scroller initialisation (see Tips and tricks below). Optionally the x and y axes can be excluded, or a custom layer backing triggering class can be supplied (see the `hwAccelerationClass` option for the constructor).
99 | * `getAppendedHTML([excludeXAxis, excludeYAxis, hwAccelerationClass, scrollbars])` - Provides the second half of the HTML that is used to construct the scroller DOM, for use to save a DOM manipulation on Scroller initialisation (see Tips and tricks below). Optionally the x and y axes can be excluded, or a custom layer backing triggering class can be supplied (see the `hwAccelerationClass` option for the constructor). Pass a truthy value in for the `scrollbars` parameter if you are enabling scrolling. _Any parameters should match those passed in to `getPrependedHTML`._
100 |
101 |
102 | ### Events
103 |
104 | Events can be bound with the `addEventListener` method. Events are fired syncronously. Regardless of the event, the listener function will receive a single argument.
105 |
106 | * `scroll` Fired whenever the scroll position changes, continuously if an input type is modifying the position. Passes an object containing `scrollLeft` and `scrollTop` properties matching the new scroll position, eg `{scrollLeft:10, scrollTop:45}`
107 | * `scrollstart` Fired when a scroll movement starts. Passes an object with the same characteristics as `scroll`.
108 | * `scrollend` Fired when a scroll movement ends. Passes an object with the same characteristics as `scroll`.
109 | * `segmentdidchange` Fires on completion of a scroll movement, if the scroller is on a different segment to the one it was on at the start of the movement. Passes an object with `segmentX` and `segmentY` properties.
110 | * `segmentwillchange` Fires as soon as the scroll position crosses a segment boundary, during a scroll movement. Passes an object with `segmentX` and `segmentY` properties.
111 | * `reachedstart` Fires when the scroll position reaches the top or left of a scroller. Passes an object with an `axis` property indicating whether the `x` or `y` axis reached its start position.
112 | * `reachedend` Fires when the scroll position reaches the bottom or right of a scroller. Passes an object with an `axis` property indicating whether the `x` or `y` axis reached its end position.
113 |
114 | ## Compatibility
115 |
116 | FTScroller supports input via mouse or touch on the following browsers:
117 |
118 | * **Apple Mobile Safari** (iOS 3.2+ confirmed, best after 4.0+)
119 | * **Google Android Browser** (Android 2.2+ confirmed)
120 | * **Microsoft Internet Explorer 10** (Windows 8 RTM confirmed)
121 | * **RIM BlackBerry PlayBook** (2.0 confirmed, 1.0 should be fine)
122 | * **Most modern desktop browsers** (IE 9+, FF 4+, Safari 3+, Chrome 1+, Opera 11.5+ - ECMAScript 5 getters and setters are currently used)
123 |
124 | ## Tips and tricks
125 |
126 | * If you are putting together the DOM in JavaScript using HTML or a template, it's advantageous to use the prototype methods for `getPrependedHTML` and `getAppendedHTML` to add the FTScroller HTML at the same time. If the elements are added to the very start and very end of the content within the element passed in to the constructor (ignoring whitespace), the Scroller instantiation will detect this and skip the DOM manipulation step, leading to faster instantiation without an additional layout.
127 | * If scrolling is going to only occur along one axis, setting `scrollingX` or `scrollingY` to `false` as part of the construction options skips creating of the corresponding DOM element, saving memory (particularly when using hardware acceleration).
128 | * Depending on your CSS, you may find that you can't scroll quite to the bottom of your content. This is typically a **[collapsing margin](http://www.complexspiral.com/publications/uncollapsing-margins/)** issue — the contents are measured, then wrapped in additional elements, but the margins spilling _out_ of the contents can't be measured. If you see this, an easy fix is typically to put `overflow: hidden` either your scrollable element or its contents; that will act as a boundary for margins so everything can be measured, and typically has no effect on appearance when applied to the content as the scrollable content won't have dimensional limits.
129 |
130 |
131 |
132 | ## Credits and collaboration
133 |
134 | The lead developer of FTScroller is Rowan Beentje at FT Labs. All open source code released by FT Labs is licenced under the MIT licence. We welcome comments, feedback and suggestions. Please feel free to raise an issue or pull request. Enjoy.
135 |
--------------------------------------------------------------------------------
/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ftscroller",
3 | "description": "FTScroller is a cross-browser Javascript/CSS library to allow touch, mouse or scrollwheel scrolling within specified elements, with pagination, snapping and bouncing support.",
4 | "version": "0.7.0",
5 | "main": "lib/ftscroller.js",
6 | "scripts": [
7 | "lib/ftscroller.js"
8 | ],
9 | "license": "MIT"
10 | }
11 |
--------------------------------------------------------------------------------
/examples/galleryscrollers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
The container below can be scrolled vertically to read the content of the article, and can be swiped horizontally to move between articles. This example demonstrates a number of features:
26 |
27 |
The vertical scrollers have visible scrollbars and bouncing enabled, while the horizontal scroller has no scrollbars or bouncing.
28 |
The windowScrollingActiveFlag property is used to ensure that it's not possible to scroll on multiple axes simultaneously.
29 |
Snapping is used on the horizontal scroller to ensure that a scroll movement always ends properly centered on a column.
30 |
31 |
Three examples of the gallery are shown, to show the different effects of the scrollBoundary and scrollResponseBoundary options.
32 |
Example One
33 |
This example is set up as you might want to set up a genuine gallery. Scrolling responds at once, using a scrollResponseBoundary of 1, but only locks scrolling to that axis after 15 pixels using a scrollBoundary of 15. Panning takes a little more to respond, with a scrollResponseBoundary of 8, and also locks to panning using a scrollBoundary of 20. Scrolls respond instantly but can convert to pans, and scrolling is preferred over panning if the input movement is diagonal.
34 |
35 |
36 |
37 |
38 |
39 |
40 |
Carbon
41 |
Carbon (from Latin: carbo "coal") is the chemical element with symbol C and atomic number 6. As a member of group 14 on the periodic table, it is nonmetallic and tetravalent—making four electrons available to form covalent chemical bonds. There are three naturally occurring isotopes, with 12C and 13C being stable, while 14C is radioactive, decaying with a half-life of about 5,730 years. Carbon is one of the few elements known since antiquity.
42 |
There are several allotropes of carbon of which the best known are graphite, diamond, and amorphous carbon. The physical properties of carbon vary widely with the allotropic form. For example, diamond is highly transparent, while graphite is opaque and black. Diamond is among the hardest materials known, while graphite is soft enough to form a streak on paper (hence its name, from the Greek word "to write"). Diamond has a very low electrical conductivity, while graphite is a very good conductor. Under normal conditions, diamond has the highest thermal conductivity of all known materials.
43 |
All carbon allotropes are solids under normal conditions with graphite being the most thermodynamically stable form. They are chemically resistant and require high temperature to react even with oxygen. The most common oxidation state of carbon in inorganic compounds is +4, while +2 is found in carbon monoxide and other transition metal carbonyl complexes. The largest sources of inorganic carbon are limestones, dolomites and carbon dioxide, but significant quantities occur in organic deposits of coal, peat, oil and methane clathrates. Carbon forms more compounds than any other element, with almost ten million pure organic compounds described to date, which in turn are a tiny fraction of such compounds that are theoretically possible under standard conditions.
44 |
Carbon is the 15th most abundant element in the Earth's crust, and the fourth most abundant element in the universe by mass after hydrogen, helium, and oxygen. It is present in all known life forms, and in the human body carbon is the second most abundant element by mass (about 18.5%) after oxygen. This abundance, together with the unique diversity of organic compounds and their unusual polymer-forming ability at the temperatures commonly encountered on Earth, make this element the chemical basis of all known life.
45 |
46 |
Characteristics
47 |
The different forms or allotropes of carbon (see below) include the hardest naturally occurring substance, diamond, and also one of the softest known substances, graphite. Moreover, it has an affinity for bonding with other small atoms, including other carbon atoms, and is capable of forming multiple stable covalent bonds with such atoms. As a result, carbon is known to form almost ten million different compounds; the large majority of all chemical compounds. Carbon also has the highest sublimation point of all elements. At atmospheric pressure it has no melting point as its triple point is at 10.8 ± 0.2 MPa and 4,600 ± 300 K (~4,330 °C or 7,820 °F), so it sublimates at about 3,900 K.
48 |
Carbon sublimes in a carbon arc which has a temperature of about 5,800 K (5,530 °C; 9,980 °F). Thus, irrespective of its allotropic form, carbon remains solid at higher temperatures than the highest melting point metals such as tungsten or rhenium. Although thermodynamically prone to oxidation, carbon resists oxidation more effectively than elements such as iron and copper that are weaker reducing agents at room temperature.
49 |
Carbon compounds form the basis of all known life on Earth, and the carbon-nitrogen cycle provides some of the energy produced by the Sun and other stars. Although it forms an extraordinary variety of compounds, most forms of carbon are comparatively unreactive under normal conditions. At standard temperature and pressure, it resists all but the strongest oxidizers. It does not react with sulfuric acid, hydrochloric acid, chlorine or any alkalis. At elevated temperatures carbon reacts with oxygen to form carbon oxides, and will reduce such metal oxides as iron oxide to the metal. This exothermic reaction is used in the iron and steel industry to control the carbon content of steel:
50 |
51 |
Fe3O4 + 4 C(s) → 3 Fe(s) + 4 CO(g)
52 |
53 |
with sulfur to form carbon disulfide and with steam in the coal-gas reaction:
54 |
55 |
C(s) + H2O(g) → CO(g) + H2(g).
56 |
57 |
Carbon combines with some metals at high temperatures to form metallic carbides, such as the iron carbide cementite in steel, and tungsten carbide, widely used as an abrasive and for making hard tips for cutting tools.
58 |
As of 2009, graphene appears to be the strongest material ever tested. However, the process of separating it from graphite will require some technological development before it is economical enough to be used in industrial processes.
59 |
60 |
Oxygen
61 |
Oxygen is the element with atomic number 8 and represented by the symbol O. Its name derives from the Greek roots ὀξύς (oxys) ("acid", literally "sharp", referring to the sour taste of acids) and -γενής (-genēs) ("producer", literally "begetter"), because at the time of naming, it was mistakenly thought that all acids required oxygen in their composition. At standard temperature and pressure, two atoms of the element bind to form dioxygen, a very pale blue, odorless, tasteless diatomic gas with the formula O2.
62 |
Oxygen is a member of the chalcogen group on the periodic table and is a highly reactive nonmetallic element that readily forms compounds (notably oxides) with almost all other elements. Oxygen is a strong oxidizing agent and has the second-highest electronegativity of all the elements (only fluorine has a higher electronegativity). By mass, oxygen is the third-most abundant element in the universe, after hydrogen and helium and the most abundant element by mass in the Earth's crust, making up almost half of the crust's mass. Free oxygen is too chemically reactive to appear on Earth without the photosynthetic action of living organisms, which use the energy of sunlight to produce elemental oxygen from water. Elemental O2 only began to accumulate in the atmosphere after the evolutionary appearance of these organisms, roughly 2.5 billion years ago. Diatomic oxygen gas constitutes 20.8% of the volume of air.
63 |
Because it comprises most of the mass in water, oxygen comprises most of the mass of living organisms (for example, about two-thirds of the human body's mass). All major classes of structural molecules in living organisms, such as proteins, carbohydrates, and fats, contain oxygen, as do the major inorganic compounds that comprise animal shells, teeth, and bone. Elemental oxygen is produced by cyanobacteria, algae and plants, and is used in cellular respiration for all complex life. Oxygen is toxic to obligately anaerobic organisms, which were the dominant form of early life on Earth until O2 began to accumulate in the atmosphere. Another form (allotrope) of oxygen, ozone (O3), helps protect the biosphere from ultraviolet radiation with the high-altitude ozone layer, but is a pollutant near the surface where it is a by-product of smog. At even higher low earth orbit altitudes atomic oxygen is a significant presence and a cause of erosion for spacecraft.
64 |
Oxygen was independently discovered by Carl Wilhelm Scheele, in Uppsala, in 1773 or earlier, and Joseph Priestley in Wiltshire, in 1774, but Priestley is often given priority because his work was published first. The name oxygen was coined in 1777 by Antoine Lavoisier, whose experiments with oxygen helped to discredit the then-popular phlogiston theory of combustion and corrosion. Oxygen is produced industrially by fractional distillation of liquefied air, use of zeolites with pressure-cycling to concentrate oxygen from air, electrolysis of water and other means. Uses of oxygen include the production of steel, plastics and textiles; rocket propellant; oxygen therapy; and life support in aircraft, submarines, spaceflight and diving.
65 |
Characteristics
66 |
Structure
67 |
68 |
69 |
70 | Oxygen discharge (spectrum) tube
71 |
72 |
73 |
At standard temperature and pressure, oxygen is a very pale blue, odorless gas with the molecular formula O2, in which the two oxygen atoms are chemically bonded to each other with a spin triplet electron configuration. This bond has a bond order of two, and is often simplified in description as a double bond or as a combination of one two-electron bond and two three-electron bonds.
74 |
Triplet oxygen (not to be confused with ozone, O3) is the ground state of the O2 molecule. The electron configuration of the molecule has two unpaired electrons occupying two degenerate molecular orbitals. These orbitals are classified as antibonding (weakening the bond order from three to two), so the diatomic oxygen bond is weaker than the diatomic nitrogen triple bond in which all bonding molecular orbitals are filled, but some antibonding orbitals are not.
75 |
76 |
77 |
78 | A trickle of liquid oxygen is deflected by a magnetic field, illustrating its paramagnetic property
79 |
80 |
81 |
In normal triplet form, O2 molecules are paramagnetic. That is, they form a magnet in the presence of a magnetic field—because of the spin magnetic moments of the unpaired electrons in the molecule, and the negative exchange energy between neighboring O2 molecules. Liquid oxygen is attracted to a magnet to a sufficient extent that, in laboratory demonstrations, a bridge of liquid oxygen may be supported against its own weight between the poles of a powerful magnet.
82 |
Singlet oxygen is a name given to several higher-energy species of molecular O2 in which all the electron spins are paired. It is much more reactive towards common organic molecules than is molecular oxygen per se. In nature, singlet oxygen is commonly formed from water during photosynthesis, using the energy of sunlight. It is also produced in the troposphere by the photolysis of ozone by light of short wavelength, and by the immune system as a source of active oxygen. Carotenoids in photosynthetic organisms (and possibly also in animals) play a major role in absorbing energy from singlet oxygen and converting it to the unexcited ground state before it can cause harm to tissues.
83 |
84 |
85 |
86 |
87 |
88 |
Example Two
89 |
This example is set up an an example of all FTScroller instances being left at the default settings of scrollResponseBoundary: 1 and scrollBoundary: 1. This is generally undesirable with nested scrollers because finger-based input will often trigger a small scroll as the finger touches the screen, so it's hard to control the intended scroll direction.
90 |
91 |
92 |
Example Three
93 |
This example is set up with large boundaries (30px) for all scrollers or both the scrollResponseBoundary and scrollBoundary settings. This requires a reasonable move before scrolls become locked to an axis, reducing the chance of accidental scrolls or pans, but the equally high response boundary means there is no visual feedback before scrolling is locked, which is also not desirable.
This example uses a very simple setup: a <div> which is set to a height of 400px, and then an FTScroller invocation on that element. Horizontal scrolling has been disabled for a slight performance improvement as it won't be needed.
20 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
21 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
22 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
23 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
24 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
25 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
26 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
27 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
28 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
29 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
30 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
31 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
32 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
33 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
34 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
35 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
36 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
37 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
38 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
39 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
40 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
41 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
42 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
43 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
44 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
45 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
46 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
47 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
48 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
49 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
50 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
51 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
52 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
53 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
54 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
55 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
56 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
57 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
58 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
59 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
60 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
61 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
62 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
63 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
64 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
65 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
66 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
67 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
68 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
69 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
70 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
71 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
72 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
73 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
74 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
75 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
76 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
77 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
78 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
79 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
This example uses a very simple setup: a <div> which is set to a height of 400px, and then an FTScroller invocation on that element. Horizontal scrolling has been disabled for a slight performance improvement as it won't be needed.
20 |
Try clicking this input:
21 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
22 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
23 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
24 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
25 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
26 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
27 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
28 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
29 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
30 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
31 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
32 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
33 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
34 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
35 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
36 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
37 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
38 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
39 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
40 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
41 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
42 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
43 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
44 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
45 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
46 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
47 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
48 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
49 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
50 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
51 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
52 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
53 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
54 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
55 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
56 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
57 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
58 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
59 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
60 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
61 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
62 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
63 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
64 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
65 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
66 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
67 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
68 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
69 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
70 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
71 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
72 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
73 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
74 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
75 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
76 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
77 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
78 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
79 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
80 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
This entire page is scrolled using FTScroller. This may seem redundant but can be useful for snapping, or content in IFRAMEs if native subframe scrolling is not available or does not provide requried features (such as elasticity). On a desktop or laptop, drag the page up and down to scroll, or use a scrollwheel. On a touch UI, swipe up and down.
21 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
22 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
23 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
24 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
25 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
26 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
27 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
28 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
29 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
30 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
31 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
32 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
33 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
34 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
35 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
36 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
37 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
38 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
39 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
40 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
41 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
42 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
43 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
44 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
45 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
46 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
47 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
48 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
49 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
50 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
51 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
52 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
53 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
54 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
55 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
56 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
57 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
58 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
59 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
60 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
61 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
62 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
63 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
64 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
65 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
66 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
67 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
68 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
69 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
70 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
71 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
72 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
73 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
74 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
75 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
76 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
77 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
78 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
79 |
80 |
81 |
82 |
83 |
84 |
85 |
90 |
91 |
--------------------------------------------------------------------------------
/lib/ftscroller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * FTScroller: touch and mouse-based scrolling for DOM elements larger than their containers.
3 | *
4 | * While this is a rewrite, it is heavily inspired by two projects:
5 | * 1) Uxebu TouchScroll (https://github.com/davidaurelio/TouchScroll), BSD licensed:
6 | * Copyright (c) 2010 uxebu Consulting Ltd. & Co. KG
7 | * Copyright (c) 2010 David Aurelio
8 | * 2) Zynga Scroller (https://github.com/zynga/scroller), MIT licensed:
9 | * Copyright 2011, Zynga Inc.
10 | * Copyright 2011, Deutsche Telekom AG
11 | *
12 | * Includes CubicBezier:
13 | *
14 | * Copyright (C) 2008 Apple Inc. All Rights Reserved.
15 | * Copyright (C) 2010 David Aurelio. All Rights Reserved.
16 | * Copyright (C) 2010 uxebu Consulting Ltd. & Co. KG. All Rights Reserved.
17 | *
18 | * Redistribution and use in source and binary forms, with or without
19 | * modification, are permitted provided that the following conditions
20 | * are met:
21 | * 1. Redistributions of source code must retain the above copyright
22 | * notice, this list of conditions and the following disclaimer.
23 | * 2. Redistributions in binary form must reproduce the above copyright
24 | * notice, this list of conditions and the following disclaimer in the
25 | * documentation and/or other materials provided with the distribution.
26 | *
27 | * THIS SOFTWARE IS PROVIDED BY APPLE INC., DAVID AURELIO, AND UXEBU
28 | * CONSULTING LTD. & CO. KG ``AS IS'' AND ANY EXPRESS OR IMPLIED
29 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
30 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
31 | * IN NO EVENT SHALL APPLE INC. OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
32 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
36 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
37 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38 | * POSSIBILITY OF SUCH DAMAGE.
39 | *
40 | * @copyright The Financial Times Ltd [All rights reserved]
41 | * @codingstandard ftlabs-jslint
42 | * @version 0.7.0
43 | */
44 | /**
45 | * @license FTScroller is (c) 2012 The Financial Times Ltd [All rights reserved] and licensed under the MIT license.
46 | *
47 | * Inspired by Uxebu TouchScroll, (c) 2010 uxebu Consulting Ltd. & Co. KG and David Aurelio, which is BSD licensed (https://github.com/davidaurelio/TouchScroll)
48 | * Inspired by Zynga Scroller, (c) 2011 Zynga Inc and Deutsche Telekom AG, which is MIT licensed (https://github.com/zynga/scroller)
49 | * Includes CubicBezier, (c) 2008 Apple Inc [All rights reserved], (c) 2010 David Aurelio and uxebu Consulting Ltd. & Co. KG. [All rights reserved], which is 2-clause BSD licensed (see above or https://github.com/davidaurelio/TouchScroll).
50 | */
51 |
52 | /*jslint nomen: true, vars: true, browser: true, continue: true, white: true*/
53 | /*globals FTScrollerOptions*/
54 |
55 | var FTScroller, CubicBezier;
56 |
57 | (function () {
58 | 'use strict';
59 |
60 | // Determine the browser engine and prefix, trying to use the unprefixed version where available.
61 | var _vendorCSSPrefix, _vendorStylePropertyPrefix, _vendorTransformLookup,
62 | _pointerEventsPrefixed, _setPointerCapture, _releasePointerCapture, _lostPointerCapture, _trackPointerEvents, _pointerTypeTouch;
63 | if (document.createElement('div').style.transform !== undefined) {
64 | _vendorCSSPrefix = '';
65 | _vendorStylePropertyPrefix = '';
66 | _vendorTransformLookup = 'transform';
67 | } else if (window.opera && Object.prototype.toString.call(window.opera) === '[object Opera]') {
68 | _vendorCSSPrefix = '-o-';
69 | _vendorStylePropertyPrefix = 'O';
70 | _vendorTransformLookup = 'OTransform';
71 | } else if (document.documentElement.style.MozTransform !== undefined) {
72 | _vendorCSSPrefix = '-moz-';
73 | _vendorStylePropertyPrefix = 'Moz';
74 | _vendorTransformLookup = 'MozTransform';
75 | } else if (document.documentElement.style.webkitTransform !== undefined) {
76 | _vendorCSSPrefix = '-webkit-';
77 | _vendorStylePropertyPrefix = 'webkit';
78 | _vendorTransformLookup = '-webkit-transform';
79 | } else if (typeof navigator.cpuClass === 'string') {
80 | _vendorCSSPrefix = '-ms-';
81 | _vendorStylePropertyPrefix = 'ms';
82 | _vendorTransformLookup = '-ms-transform';
83 | }
84 |
85 | // Pointer Events are unprefixed in IE11
86 | if ('pointerEnabled' in window.navigator) {
87 | _pointerEventsPrefixed = false;
88 | _trackPointerEvents = window.navigator.pointerEnabled;
89 | _setPointerCapture = 'setPointerCapture';
90 | _releasePointerCapture = 'releasePointerCapture';
91 | _lostPointerCapture = 'lostpointercapture';
92 | _pointerTypeTouch = 'touch';
93 | } else if ('msPointerEnabled' in window.navigator) {
94 | _pointerEventsPrefixed = true;
95 | _trackPointerEvents = window.navigator.msPointerEnabled;
96 | _setPointerCapture = 'msSetPointerCapture';
97 | _releasePointerCapture = 'msReleasePointerCapture';
98 | _lostPointerCapture = 'MSLostPointerCapture';
99 | _pointerTypeTouch = 2; // PointerEvent.MSPOINTER_TYPE_TOUCH = 2 in IE10
100 | }
101 |
102 | // Global flag to determine if any scroll is currently active. This prevents
103 | // issues when using multiple scrollers, particularly when they're nested.
104 | var _ftscrollerMoving = false;
105 |
106 | // Determine whether pointer events or touch events can be used
107 | var _trackTouchEvents = !_trackPointerEvents;
108 |
109 | // Determine whether to use modern hardware acceleration rules or dynamic/toggleable rules.
110 | // Certain older browsers - particularly Android browsers - have problems with hardware
111 | // acceleration, so being able to toggle the behaviour dynamically via a CSS cascade is desirable.
112 | var _useToggleableHardwareAcceleration = false;
113 | if ('hasOwnProperty' in window) {
114 | _useToggleableHardwareAcceleration = !window.hasOwnProperty('ArrayBuffer');
115 | }
116 |
117 | // Feature detection
118 | var _canClearSelection = (window.Selection && window.Selection.prototype.removeAllRanges);
119 |
120 | // If hardware acceleration is using the standard path, but perspective doesn't seem to be supported,
121 | // 3D transforms likely aren't supported either
122 | if (!_useToggleableHardwareAcceleration && document.createElement('div').style[_vendorStylePropertyPrefix + (_vendorStylePropertyPrefix ? 'P' : 'p') + 'erspective'] === undefined) {
123 | _useToggleableHardwareAcceleration = true;
124 | }
125 |
126 | // Style prefixes
127 | var _transformProperty = _vendorStylePropertyPrefix + (_vendorStylePropertyPrefix ? 'T' : 't') + 'ransform';
128 | var _transitionProperty = _vendorStylePropertyPrefix + (_vendorStylePropertyPrefix ? 'T' : 't') + 'ransition';
129 | var _translateRulePrefix = _useToggleableHardwareAcceleration ? 'translate(' : 'translate3d(';
130 | var _transformPrefixes = { x: '', y: '0,' };
131 | var _transformSuffixes = { x: ',0' + (_useToggleableHardwareAcceleration ? ')' : ',0)'), y: (_useToggleableHardwareAcceleration ? ')' : ',0)') };
132 |
133 | // Constants. Note that the bezier curve should be changed along with the friction!
134 | var _kFriction = 0.998;
135 | var _kMinimumSpeed = 0.01;
136 |
137 | // Create a global stylesheet to set up stylesheet rules and track dynamic entries
138 | (function () {
139 | var stylesheetContainerNode = document.getElementsByTagName('head')[0] || document.documentElement;
140 | var newStyleNode = document.createElement('style');
141 | var hardwareAccelerationRule;
142 | var _styleText;
143 | newStyleNode.type = 'text/css';
144 |
145 | // Determine the hardware acceleration logic to use
146 | if (_useToggleableHardwareAcceleration) {
147 | hardwareAccelerationRule = _vendorCSSPrefix + 'transform-style: preserve-3d;';
148 | } else {
149 | hardwareAccelerationRule = _vendorCSSPrefix + 'transform: translateZ(0);';
150 | }
151 |
152 | // Add our rules
153 | _styleText = [
154 | '.ftscroller_container { overflow: hidden; position: relative; max-height: 100%; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -ms-touch-action: none }',
155 | '.ftscroller_hwaccelerated { ' + hardwareAccelerationRule + ' }',
156 | '.ftscroller_x, .ftscroller_y { position: relative; min-width: 100%; min-height: 100%; overflow: hidden }',
157 | '.ftscroller_x { display: inline-block }',
158 | '.ftscroller_scrollbar { pointer-events: none; position: absolute; width: 5px; height: 5px; border: 1px solid rgba(255, 255, 255, 0.3); -webkit-border-radius: 3px; border-radius: 6px; opacity: 0; ' + _vendorCSSPrefix + 'transition: opacity 350ms; z-index: 10; -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box }',
159 | '.ftscroller_scrollbarx { bottom: 2px; left: 2px }',
160 | '.ftscroller_scrollbary { right: 2px; top: 2px }',
161 | '.ftscroller_scrollbarinner { height: 100%; background: #000; -webkit-border-radius: 2px; border-radius: 4px / 6px }',
162 | '.ftscroller_scrollbar.active { opacity: 0.5; ' + _vendorCSSPrefix + 'transition: none; -o-transition: all 0 none }'
163 | ];
164 |
165 | if (newStyleNode.styleSheet) {
166 | newStyleNode.styleSheet.cssText = _styleText.join('\n');
167 | } else {
168 | newStyleNode.appendChild(document.createTextNode(_styleText.join('\n')));
169 | }
170 |
171 | // Add the stylesheet
172 | stylesheetContainerNode.insertBefore(newStyleNode, stylesheetContainerNode.firstChild);
173 | }());
174 |
175 | /**
176 | * Master constructor for the scrolling function, including which element to
177 | * construct the scroller in, and any scrolling options.
178 | * Note that app-wide options can also be set using a global FTScrollerOptions
179 | * object.
180 | */
181 | FTScroller = function (domNode, options) {
182 | var key;
183 | var destroy, setSnapSize, scrollTo, scrollBy, updateDimensions, addEventListener, removeEventListener, setDisabledInputMethods, _startScroll, _updateScroll, _endScroll, _finalizeScroll, _interruptScroll, _flingScroll, _snapScroll, _getSnapPositionForIndexes, _getSnapIndexForPosition, _constrainAndRenderTargetScrollPosition, _limitToBounds, _initializeDOM, _existingDOMValid, _domChanged, _updateDimensions, _updateScrollbarDimensions, _updateElementPosition, _updateSegments, _setAxisPosition, _getPosition, _scheduleAxisPosition, _fireEvent, _childFocused, _modifyDistanceBeyondBounds, _distancesBeyondBounds, _startAnimation, _scheduleRender, _cancelAnimation, _addEventHandlers, _removeEventHandlers, _resetEventHandlers, _onTouchStart, _onTouchMove, _onTouchEnd, _onMouseDown, _onMouseMove, _onMouseUp, _onPointerDown, _onPointerMove, _onPointerUp, _onPointerCancel, _onPointerCaptureEnd, _onClick, _onMouseScroll, _captureInput, _releaseInputCapture, _getBoundingRect;
184 |
185 |
186 | /* Note that actual object instantiation occurs at the end of the closure to avoid jslint errors */
187 |
188 |
189 | /* Options */
190 |
191 | var _instanceOptions = {
192 |
193 | // Whether to display scrollbars as appropriate
194 | scrollbars: true,
195 |
196 | // Enable scrolling on the X axis if content is available
197 | scrollingX: true,
198 |
199 | // Enable scrolling on the Y axis if content is available
200 | scrollingY: true,
201 |
202 | // The initial movement required to trigger a scroll, in pixels; this is the point at which
203 | // the scroll is exclusive to this particular FTScroller instance.
204 | scrollBoundary: 1,
205 |
206 | // The initial movement required to trigger a visual indication that scrolling is occurring,
207 | // in pixels. This is enforced to be less than or equal to the scrollBoundary, and is used to
208 | // define when the scroller starts drawing changes in response to an input, even if the scroll
209 | // is not treated as having begun/locked yet.
210 | scrollResponseBoundary: 1,
211 |
212 | // Whether to always enable scrolling, even if the content of the scroller does not
213 | // require the scroller to function. This makes the scroller behave more like an
214 | // element set to "overflow: scroll", with bouncing always occurring if enabled.
215 | alwaysScroll: false,
216 |
217 | // The content width to use when determining scroller dimensions. If this
218 | // is false, the width will be detected based on the actual content.
219 | contentWidth: undefined,
220 |
221 | // The content height to use when determining scroller dimensions. If this
222 | // is false, the height will be detected based on the actual content.
223 | contentHeight: undefined,
224 |
225 | // Enable snapping of content to 'pages' or a pixel grid
226 | snapping: false,
227 |
228 | // Define the horizontal interval of the pixel grid; snapping must be enabled for this to
229 | // take effect. If this is not defined, snapping will use intervals based on container size.
230 | snapSizeX: undefined,
231 |
232 | // Define the vertical interval of the pixel grid; snapping must be enabled for this to
233 | // take effect. If this is not defined, snapping will use intervals based on container size.
234 | snapSizeY: undefined,
235 |
236 | // Control whether snapping should be curtailed to only ever flick to the next page
237 | // and not beyond. Snapping needs to be enabled for this to take effect.
238 | singlePageScrolls: false,
239 |
240 | // Allow scroll bouncing and elasticity near the ends and grid
241 | bouncing: true,
242 |
243 | // Allow a fast scroll to continue with momentum when released
244 | flinging: true,
245 |
246 | // Automatically detects changes to the contained markup and
247 | // updates its dimensions whenever the content changes. This is
248 | // set to false if a contentWidth or contentHeight are supplied.
249 | updateOnChanges: true,
250 |
251 | // Automatically catches changes to the window size and updates
252 | // its dimensions.
253 | updateOnWindowResize: false,
254 |
255 | // The alignment to use if the content is smaller than the container;
256 | // this also applies to initial positioning of scrollable content.
257 | // Valid alignments are -1 (top or left), 0 (center), and 1 (bottom or right).
258 | baseAlignments: { x: -1, y: -1 },
259 |
260 | // Whether to use a window scroll flag, eg window.foo, to control whether
261 | // to allow scrolling to start or now. If the window flag is set to true,
262 | // this element will not start scrolling; this element will also toggle
263 | // the variable while scrolling
264 | windowScrollingActiveFlag: undefined,
265 |
266 | // Instead of always using translate3d for transforms, a mix of translate3d
267 | // and translate with a hardware acceleration class used to trigger acceleration
268 | // is used; this is to allow CSS inheritance to be used to allow dynamic
269 | // disabling of backing layers on older platforms.
270 | hwAccelerationClass: 'ftscroller_hwaccelerated',
271 |
272 | // While use of requestAnimationFrame is highly recommended on platforms
273 | // which support it, it can result in the animation being a further half-frame
274 | // behind the input method, increasing perceived lag slightly. To disable this,
275 | // set this property to false.
276 | enableRequestAnimationFrameSupport: true,
277 |
278 | // Set the maximum time (ms) that a fling can take to complete; if
279 | // this is not set, flings will complete instantly
280 | maxFlingDuration: 1000,
281 |
282 | // Whether to disable any input methods; on some multi-input devices
283 | // custom behaviour may be desired for some scrollers. Use with care!
284 | disabledInputMethods: {
285 | mouse: false,
286 | touch: false,
287 | scroll: false,
288 | pointer: false,
289 | focus: false
290 | },
291 |
292 | // Define a scrolling class to be added to the scroller container
293 | // when scrolling is active. Note that this can cause a relayout on
294 | // scroll start if defined, but allows custom styling in response to scrolls
295 | scrollingClassName: undefined,
296 |
297 | // Bezier curves defining the feel of the fling (momentum) deceleration,
298 | // the bounce decleration deceleration (as a fling exceeds the bounds),
299 | // and the bounce bezier (used for bouncing back).
300 | flingBezier: new CubicBezier(0.103, 0.389, 0.307, 0.966),
301 | bounceDecelerationBezier: new CubicBezier(0, 0.5, 0.5, 1),
302 | bounceBezier: new CubicBezier(0.7, 0, 0.9, 0.6),
303 |
304 | // If the scroller is constrained to an x axis, convert y scroll to allow single-axis scroll
305 | // wheels to scroll constrained content.
306 | invertScrollWheel: true
307 | };
308 |
309 |
310 | /* Local variables */
311 |
312 | // Cache the DOM node and set up variables for other nodes
313 | var _publicSelf;
314 | var _self = this;
315 | var _scrollableMasterNode = domNode;
316 | var _containerNode;
317 | var _contentParentNode;
318 | var _scrollNodes = { x: null, y: null };
319 | var _scrollbarNodes = { x: null, y: null };
320 |
321 | // Dimensions of the container element and the content element
322 | var _metrics = {
323 | container: { x: null, y: null },
324 | content: { x: null, y: null, rawX: null, rawY: null },
325 | scrollEnd: { x: null, y: null }
326 | };
327 |
328 | // Snapping details
329 | var _snapGridSize = {
330 | x: false,
331 | y: false,
332 | userX: false,
333 | userY: false
334 | };
335 | var _snapIndex = {
336 | x: 0,
337 | y: 0
338 | };
339 | var _baseSegment = { x: 0, y: 0 };
340 | var _activeSegment = { x: 0, y: 0 };
341 |
342 | // Track the identifier of any input being tracked
343 | var _inputIdentifier = false;
344 | var _inputIndex = 0;
345 | var _inputCaptured = false;
346 |
347 | // Current scroll positions and tracking
348 | var _isScrolling = false;
349 | var _isDisplayingScroll = false;
350 | var _isAnimating = false;
351 | var _baseScrollPosition = { x: 0, y: 0 };
352 | var _lastScrollPosition = { x: 0, y: 0 };
353 | var _targetScrollPosition = { x: 0, y: 0 };
354 | var _scrollAtExtremity = { x: null, y: null };
355 | var _preventClick = false;
356 | var _timeouts = [];
357 | var _hasBeenScrolled = false;
358 |
359 | // Gesture details
360 | var _baseScrollableAxes = {};
361 | var _scrollableAxes = { x: true, y: true };
362 | var _gestureStart = { x: 0, y: 0, t: 0 };
363 | var _cumulativeScroll = { x: 0, y: 0 };
364 | var _eventHistory = [];
365 |
366 | // Allow certain events to be debounced
367 | var _domChangeDebouncer = false;
368 | var _scrollWheelEndDebouncer = false;
369 |
370 | // Performance switches on browsers supporting requestAnimationFrame
371 | var _animationFrameRequest = false;
372 | var _reqAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || false;
373 | var _cancelAnimationFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame || window.mozCancelAnimationFrame || window.mozCancelRequestAnimationFrame || window.webkitCancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.msCancelAnimationFrame || window.msCancelRequestAnimationFrame || false;
374 |
375 | // Event listeners
376 | var _eventListeners = {
377 | 'scrollstart': [],
378 | 'scroll': [],
379 | 'scrollend': [],
380 | 'segmentwillchange': [],
381 | 'segmentdidchange': [],
382 | 'reachedstart': [],
383 | 'reachedend': [],
384 | 'scrollinteractionend': []
385 | };
386 |
387 | // MutationObserver instance, when supported and if DOM change sniffing is enabled
388 | var _mutationObserver;
389 |
390 |
391 | /* Parsing supplied options */
392 |
393 | // Override default instance options with global - or closure'd - options
394 | if (typeof FTScrollerOptions === 'object' && FTScrollerOptions) {
395 | for (key in FTScrollerOptions) {
396 | if (FTScrollerOptions.hasOwnProperty(key) && _instanceOptions.hasOwnProperty(key)) {
397 | _instanceOptions[key] = FTScrollerOptions[key];
398 | }
399 | }
400 | }
401 |
402 | // Override default and global options with supplied options
403 | if (options) {
404 | for (key in options) {
405 | if (options.hasOwnProperty(key)) {
406 |
407 | // If a deprecated flag was passed in, warn, and convert to the new flag name
408 | if ('paginatedSnap' === key) {
409 | console.warn('FTScroller: "paginatedSnap" is deprecated; converting to "singlePageScrolls"');
410 | _instanceOptions.singlePageScrolls = options.paginatedSnap;
411 | continue;
412 | }
413 |
414 | if (_instanceOptions.hasOwnProperty(key)) {
415 | _instanceOptions[key] = options[key];
416 | }
417 | }
418 | }
419 |
420 | // If snap grid size options were supplied, store them
421 | if (options.hasOwnProperty('snapSizeX') && !isNaN(options.snapSizeX)) {
422 | _snapGridSize.userX = _snapGridSize.x = options.snapSizeX;
423 | }
424 | if (options.hasOwnProperty('snapSizeY') && !isNaN(options.snapSizeY)) {
425 | _snapGridSize.userY = _snapGridSize.y = options.snapSizeY;
426 | }
427 |
428 | // If content width and height were defined, disable updateOnChanges for performance
429 | if (options.contentWidth && options.contentHeight) {
430 | options.updateOnChanges = false;
431 | }
432 | }
433 |
434 | // Validate the scroll response parameter
435 | _instanceOptions.scrollResponseBoundary = Math.min(_instanceOptions.scrollBoundary, _instanceOptions.scrollResponseBoundary);
436 |
437 | // Update base scrollable axes
438 | if (_instanceOptions.scrollingX) {
439 | _baseScrollableAxes.x = true;
440 | }
441 | if (_instanceOptions.scrollingY) {
442 | _baseScrollableAxes.y = true;
443 | }
444 |
445 | // Only enable animation frame support if the instance options permit it
446 | _reqAnimationFrame = _instanceOptions.enableRequestAnimationFrameSupport && _reqAnimationFrame;
447 | _cancelAnimationFrame = _reqAnimationFrame && _cancelAnimationFrame;
448 |
449 |
450 | /* Scoped Functions */
451 |
452 | /**
453 | * Unbinds all event listeners to prevent circular references preventing items
454 | * from being deallocated, and clean up references to dom elements. Pass in
455 | * "removeElements" to also remove FTScroller DOM elements for special reuse cases.
456 | */
457 | destroy = function destroy(removeElements) {
458 | var i, l;
459 |
460 | _removeEventHandlers();
461 | _cancelAnimation();
462 | if (_domChangeDebouncer) {
463 | window.clearTimeout(_domChangeDebouncer);
464 | _domChangeDebouncer = false;
465 | }
466 | for (i = 0, l = _timeouts.length; i < l; i = i + 1) {
467 | window.clearTimeout(_timeouts[i]);
468 | }
469 | _timeouts.length = 0;
470 |
471 | // Destroy DOM elements if required
472 | if (removeElements && _scrollableMasterNode) {
473 | while (_contentParentNode.firstChild) {
474 | _scrollableMasterNode.appendChild(_contentParentNode.firstChild);
475 | }
476 | _scrollableMasterNode.removeChild(_containerNode);
477 | }
478 |
479 | _scrollableMasterNode = null;
480 | _containerNode = null;
481 | _contentParentNode = null;
482 | _scrollNodes.x = null;
483 | _scrollNodes.y = null;
484 | _scrollbarNodes.x = null;
485 | _scrollbarNodes.y = null;
486 | for (i in _eventListeners) {
487 | if (_eventListeners.hasOwnProperty(i)) {
488 | _eventListeners[i].length = 0;
489 | }
490 | }
491 |
492 | // If this is currently tracked as a scrolling instance, clear the flag
493 | if (_ftscrollerMoving && _ftscrollerMoving === _self) {
494 | _ftscrollerMoving = false;
495 | if (_instanceOptions.windowScrollingActiveFlag) {
496 | window[_instanceOptions.windowScrollingActiveFlag] = false;
497 | }
498 | }
499 | };
500 |
501 | /**
502 | * Configures the snapping boundaries within the scrolling element if
503 | * snapping is active. If this is never called, snapping defaults to
504 | * using the bounding box, eg page-at-a-time.
505 | */
506 | setSnapSize = function setSnapSize(width, height) {
507 | _snapGridSize.userX = width;
508 | _snapGridSize.userY = height;
509 | _snapGridSize.x = width;
510 | _snapGridSize.y = height;
511 |
512 | // Ensure the content dimensions conform to the grid
513 | _metrics.content.x = Math.ceil(_metrics.content.rawX / width) * width;
514 | _metrics.content.y = Math.ceil(_metrics.content.rawY / height) * height;
515 | _metrics.scrollEnd.x = _metrics.container.x - _metrics.content.x;
516 | _metrics.scrollEnd.y = _metrics.container.y - _metrics.content.y;
517 | _updateScrollbarDimensions();
518 |
519 | // Snap to the new grid if necessary
520 | _snapScroll();
521 | _updateSegments(true);
522 | };
523 |
524 | /**
525 | * Scroll to a supplied position, including whether or not to animate the
526 | * scroll and how fast to perform the animation (pass in true to select a
527 | * dynamic duration). The inputs will be constrained to bounds and snapped.
528 | * If false is supplied for a position, that axis will not be scrolled.
529 | */
530 | scrollTo = function scrollTo(left, top, animationDuration) {
531 | var targetPosition, duration, positions, axis, maxDuration = 0, scrollPositionsToApply = {};
532 |
533 | // If a manual scroll is in progress, cancel it
534 | _endScroll(Date.now());
535 |
536 | // Move supplied coordinates into an object for iteration, also inverting the values into
537 | // our coordinate system
538 | positions = {
539 | x: -left,
540 | y: -top
541 | };
542 |
543 | for (axis in _baseScrollableAxes) {
544 | if (_baseScrollableAxes.hasOwnProperty(axis)) {
545 | targetPosition = positions[axis];
546 | if (targetPosition === false) {
547 | continue;
548 | }
549 |
550 | // Constrain to bounds
551 | targetPosition = Math.min(0, Math.max(_metrics.scrollEnd[axis], targetPosition));
552 |
553 | // Snap if appropriate
554 | if (_instanceOptions.snapping && _snapGridSize[axis]) {
555 | targetPosition = Math.round(targetPosition / _snapGridSize[axis]) * _snapGridSize[axis];
556 | }
557 |
558 | // Get a duration
559 | duration = animationDuration || 0;
560 | if (duration === true) {
561 | duration = Math.sqrt(Math.abs(_baseScrollPosition[axis] - targetPosition)) * 20;
562 | }
563 |
564 | // Trigger the position change
565 | _setAxisPosition(axis, targetPosition, duration);
566 | scrollPositionsToApply[axis] = targetPosition;
567 | maxDuration = Math.max(maxDuration, duration);
568 | }
569 | }
570 |
571 | // If the scroll had resulted in a change in position, perform some additional actions:
572 | if (_baseScrollPosition.x !== positions.x || _baseScrollPosition.y !== positions.y) {
573 |
574 | // Mark a scroll as having ever occurred
575 | _hasBeenScrolled = true;
576 |
577 | // If an animation duration is present, fire a scroll start event and a
578 | // scroll event for any listeners to act on
579 | _fireEvent('scrollstart', _getPosition());
580 | _fireEvent('scroll', _getPosition());
581 | }
582 |
583 | if (maxDuration) {
584 | _timeouts.push(setTimeout(function () {
585 | var anAxis;
586 | for (anAxis in scrollPositionsToApply) {
587 | if (scrollPositionsToApply.hasOwnProperty(anAxis)) {
588 | _lastScrollPosition[anAxis] = scrollPositionsToApply[anAxis];
589 | }
590 | }
591 | _finalizeScroll();
592 | }, maxDuration));
593 | } else {
594 | _finalizeScroll();
595 | }
596 | };
597 |
598 | /**
599 | * Alter the current scroll position, including whether or not to animate
600 | * the scroll and how fast to perform the animation (pass in true to
601 | * select a dynamic duration). The inputs will be checked against the
602 | * current position.
603 | */
604 | scrollBy = function scrollBy(horizontal, vertical, animationDuration) {
605 |
606 | // Wrap the scrollTo function for simplicity
607 | scrollTo(parseFloat(horizontal) - _baseScrollPosition.x, parseFloat(vertical) - _baseScrollPosition.y, animationDuration);
608 | };
609 |
610 | /**
611 | * Provide a public method to detect changes in dimensions for either the content or the
612 | * container.
613 | */
614 | updateDimensions = function updateDimensions(contentWidth, contentHeight, ignoreSnapScroll) {
615 | options.contentWidth = contentWidth || options.contentWidth;
616 | options.contentHeight = contentHeight || options.contentHeight;
617 |
618 | // Currently just wrap the private API
619 | _updateDimensions(!!ignoreSnapScroll);
620 | };
621 |
622 | /**
623 | * Add an event handler for a supported event. Current events include:
624 | * scroll - fired whenever the scroll position changes
625 | * scrollstart - fired when a scroll movement starts
626 | * scrollend - fired when a scroll movement ends
627 | * segmentwillchange - fired whenever the segment changes, including during scrolling
628 | * segmentdidchange - fired when a segment has conclusively changed, after scrolling.
629 | */
630 | addEventListener = function addEventListener(eventname, eventlistener) {
631 |
632 | // Ensure this is a valid event
633 | if (!_eventListeners.hasOwnProperty(eventname)) {
634 | return false;
635 | }
636 |
637 | // Add the listener
638 | _eventListeners[eventname].push(eventlistener);
639 | return true;
640 | };
641 |
642 | /**
643 | * Remove an event handler for a supported event. The listener must be exactly the same as
644 | * an added listener to be removed.
645 | */
646 | removeEventListener = function removeEventListener(eventname, eventlistener) {
647 | var i;
648 |
649 | // Ensure this is a valid event
650 | if (!_eventListeners.hasOwnProperty(eventname)) {
651 | return false;
652 | }
653 |
654 | for (i = _eventListeners[eventname].length; i >= 0; i = i - 1) {
655 | if (_eventListeners[eventname][i] === eventlistener) {
656 | _eventListeners[eventname].splice(i, 1);
657 | }
658 | }
659 | return true;
660 | };
661 |
662 | /**
663 | * Set the input methods to disable. No inputs methods are disabled by default.
664 | * (object, default { mouse: false, touch: false, scroll: false, pointer: false, focus: false })
665 | */
666 | setDisabledInputMethods = function setDisabledInputMethods(disabledInputMethods) {
667 | var i, changed;
668 |
669 | for (i in _instanceOptions.disabledInputMethods) {
670 | disabledInputMethods[i] = !!disabledInputMethods[i];
671 |
672 | if (_instanceOptions.disabledInputMethods[i] !== disabledInputMethods[i]) changed = true;
673 | _instanceOptions.disabledInputMethods[i] = disabledInputMethods[i];
674 | }
675 |
676 | if (changed) {
677 | _resetEventHandlers();
678 | }
679 | };
680 |
681 | /**
682 | * Start a scroll tracking input - this could be mouse, webkit-style touch,
683 | * or ms-style pointer events.
684 | */
685 | _startScroll = function _startScroll(inputX, inputY, inputTime, rawEvent) {
686 | var triggerScrollInterrupt = _isAnimating;
687 |
688 | // Opera fix
689 | if (inputTime <= 0) {
690 | inputTime = Date.now();
691 | }
692 |
693 | // If a window scrolling flag is set, and evaluates to true, don't start checking touches
694 | if (_instanceOptions.windowScrollingActiveFlag && window[_instanceOptions.windowScrollingActiveFlag]) {
695 | return false;
696 | }
697 |
698 | // If an animation is in progress, stop the scroll.
699 | if (triggerScrollInterrupt) {
700 | _interruptScroll();
701 | } else {
702 |
703 | // Allow clicks again, but only if a scroll was not interrupted
704 | _preventClick = false;
705 | }
706 |
707 | // Store the initial event coordinates
708 | _gestureStart.x = inputX;
709 | _gestureStart.y = inputY;
710 | _gestureStart.t = inputTime;
711 | _targetScrollPosition.x = _lastScrollPosition.x;
712 | _targetScrollPosition.y = _lastScrollPosition.y;
713 |
714 | // Clear event history and add the start touch
715 | _eventHistory.length = 0;
716 | _eventHistory.push({ x: inputX, y: inputY, t: inputTime });
717 |
718 | if (triggerScrollInterrupt) {
719 | _updateScroll(inputX, inputY, inputTime, rawEvent, triggerScrollInterrupt);
720 | }
721 |
722 | return true;
723 | };
724 |
725 | /**
726 | * Continue a scroll as a result of an updated position
727 | */
728 | _updateScroll = function _updateScroll(inputX, inputY, inputTime, rawEvent, scrollInterrupt) {
729 | var axis, otherScrollerActive, distancesBeyondBounds;
730 | var initialScroll = false;
731 | var gesture = {
732 | x: inputX - _gestureStart.x,
733 | y: inputY - _gestureStart.y
734 | };
735 |
736 | // Opera fix
737 | if (inputTime <= 0) {
738 | inputTime = Date.now();
739 | }
740 |
741 | // Update base target positions
742 | _targetScrollPosition.x = _baseScrollPosition.x + gesture.x;
743 | _targetScrollPosition.y = _baseScrollPosition.y + gesture.y;
744 |
745 | // If scrolling has not yet locked to this scroller, check whether to stop scrolling
746 | if (!_isScrolling) {
747 |
748 | // Check the internal flag to determine if another FTScroller is scrolling
749 | if (_ftscrollerMoving && _ftscrollerMoving !== _self) {
750 | otherScrollerActive = true;
751 | }
752 |
753 | // Otherwise, check the window scrolling flag to see if anything else has claimed scrolling
754 | else if (_instanceOptions.windowScrollingActiveFlag && window[_instanceOptions.windowScrollingActiveFlag]) {
755 | otherScrollerActive = true;
756 | }
757 |
758 | // If another scroller was active, clean up and stop processing.
759 | if (otherScrollerActive) {
760 | _releaseInputCapture();
761 | _inputIdentifier = false;
762 | if (_isDisplayingScroll) {
763 | _cancelAnimation();
764 | if (!_snapScroll(true)) {
765 | _finalizeScroll(true);
766 | }
767 | }
768 | return;
769 | }
770 | }
771 |
772 | // If not yet displaying a scroll, determine whether that triggering boundary
773 | // has been exceeded
774 | if (!_isDisplayingScroll) {
775 |
776 | // Determine scroll distance beyond bounds
777 | distancesBeyondBounds = _distancesBeyondBounds(_targetScrollPosition);
778 |
779 | // Check scrolled distance against the boundary limit to see if scrolling can be triggered.
780 | // If the scroll has been interrupted, trigger at once
781 | if (!scrollInterrupt && (!_scrollableAxes.x || Math.abs(gesture.x) < _instanceOptions.scrollResponseBoundary) && (!_scrollableAxes.y || Math.abs(gesture.y) < _instanceOptions.scrollResponseBoundary)) {
782 | return;
783 | }
784 |
785 | // Determine whether to prevent the default scroll event - if the scroll could still
786 | // be triggered, prevent the default to avoid problems (particularly on PlayBook)
787 | if (_instanceOptions.bouncing || scrollInterrupt || (_scrollableAxes.x && gesture.x && distancesBeyondBounds.x < 0) || (_scrollableAxes.y && gesture.y && distancesBeyondBounds.y < 0)) {
788 | rawEvent.preventDefault();
789 | }
790 |
791 | // If bouncing is disabled, and already at an edge and scrolling beyond the edge, ignore the scroll for
792 | // now - this allows other scrollers to claim if appropriate, allowing nicer nested scrolls.
793 | if (!_instanceOptions.bouncing && !scrollInterrupt && (!_scrollableAxes.x || !gesture.x || distancesBeyondBounds.x > 0) && (!_scrollableAxes.y || !gesture.y || distancesBeyondBounds.y > 0)) {
794 |
795 | // Prevent the original click now that scrolling would be triggered
796 | _preventClick = true;
797 |
798 | return;
799 | }
800 |
801 | // Trigger the start of visual scrolling
802 | _startAnimation();
803 | _isDisplayingScroll = true;
804 | _hasBeenScrolled = true;
805 | _isAnimating = true;
806 | initialScroll = true;
807 | } else {
808 |
809 | // Prevent the event default. It is safe to call this in IE10 because the event is never
810 | // a window.event, always a "true" event.
811 | rawEvent.preventDefault();
812 | }
813 |
814 | // If not yet locked to a scroll, determine whether to do so
815 | if (!_isScrolling) {
816 |
817 | // If the gesture distance has exceeded the scroll lock distance, or snapping is active
818 | // and the scroll has been interrupted, enter exclusive scrolling.
819 | if ((scrollInterrupt && _instanceOptions.snapping) || (_scrollableAxes.x && Math.abs(gesture.x) >= _instanceOptions.scrollBoundary) || (_scrollableAxes.y && Math.abs(gesture.y) >= _instanceOptions.scrollBoundary)) {
820 |
821 | _isScrolling = true;
822 | _preventClick = true;
823 | _ftscrollerMoving = _self;
824 | if (_instanceOptions.windowScrollingActiveFlag) {
825 | window[_instanceOptions.windowScrollingActiveFlag] = _self;
826 | }
827 | _fireEvent('scrollstart', _getPosition());
828 | }
829 | }
830 |
831 | // Capture pointer if necessary
832 | if (_isScrolling) {
833 | _captureInput();
834 | }
835 |
836 | // Cancel text selections while dragging a cursor
837 | if (_canClearSelection) {
838 | window.getSelection().removeAllRanges();
839 | }
840 |
841 | // Ensure the target scroll position is affected by bounds and render if needed
842 | _constrainAndRenderTargetScrollPosition();
843 |
844 | // To aid render/draw coalescing, perform other one-off actions here
845 | if (initialScroll) {
846 | if (gesture.x > 0) {
847 | _baseScrollPosition.x -= _instanceOptions.scrollResponseBoundary;
848 | } else if(gesture.x < 0) {
849 | _baseScrollPosition.x += _instanceOptions.scrollResponseBoundary;
850 | }
851 |
852 | if (gesture.y > 0) {
853 | _baseScrollPosition.y -= _instanceOptions.scrollResponseBoundary;
854 | } else if(gesture.y < 0) {
855 | _baseScrollPosition.y += _instanceOptions.scrollResponseBoundary;
856 | }
857 |
858 | _targetScrollPosition.x = _baseScrollPosition.x + gesture.x;
859 | _targetScrollPosition.y = _baseScrollPosition.y + gesture.y;
860 |
861 | if (_instanceOptions.scrollingClassName) {
862 | _containerNode.className += ' ' + _instanceOptions.scrollingClassName;
863 | }
864 | if (_instanceOptions.scrollbars) {
865 | for (axis in _scrollableAxes) {
866 | if (_scrollableAxes.hasOwnProperty(axis)) {
867 | _scrollbarNodes[axis].className += ' active';
868 | }
869 | }
870 | }
871 | }
872 |
873 | // Add an event to the event history, keeping it around twenty events long
874 | _eventHistory.push({ x: inputX, y: inputY, t: inputTime });
875 | if (_eventHistory.length > 30) {
876 | _eventHistory.splice(0, 15);
877 | }
878 | };
879 |
880 | /**
881 | * Complete a scroll with a final event time if available (it may
882 | * not be, depending on the input type); this may continue the scroll
883 | * with a fling and/or bounceback depending on options.
884 | */
885 | _endScroll = function _endScroll(inputTime, rawEvent) {
886 | _releaseInputCapture();
887 | _inputIdentifier = false;
888 | _cancelAnimation();
889 |
890 | _fireEvent('scrollinteractionend', {});
891 |
892 | if (!_isScrolling) {
893 | if (!_snapScroll(true) && _isDisplayingScroll) {
894 | _finalizeScroll(true);
895 | }
896 | return;
897 | }
898 |
899 | // Modify the last movement event to include the end event time
900 | _eventHistory[_eventHistory.length - 1].t = inputTime;
901 |
902 | // Update flags
903 | _isScrolling = false;
904 | _isDisplayingScroll = false;
905 | _ftscrollerMoving = false;
906 | if (_instanceOptions.windowScrollingActiveFlag) {
907 | window[_instanceOptions.windowScrollingActiveFlag] = false;
908 | }
909 |
910 | // Stop the event default. It is safe to call this in IE10 because
911 | // the event is never a window.event, always a "true" event.
912 | if (rawEvent) {
913 | rawEvent.preventDefault();
914 | }
915 |
916 | // Trigger a fling or bounceback if necessary
917 | if (!_flingScroll() && !_snapScroll()) {
918 | _finalizeScroll();
919 | }
920 | };
921 |
922 | /**
923 | * Remove the scrolling class, cleaning up display.
924 | */
925 | _finalizeScroll = function _finalizeScroll(scrollCancelled) {
926 | var i, l, axis, scrollEvent, scrollRegex;
927 |
928 | _isAnimating = false;
929 | _isDisplayingScroll = false;
930 |
931 | // Remove scrolling class if set
932 | if (_instanceOptions.scrollingClassName) {
933 | scrollRegex = new RegExp('(?:^|\\s)' + _instanceOptions.scrollingClassName + '(?!\\S)', 'g');
934 | _containerNode.className = _containerNode.className.replace(scrollRegex, '');
935 | }
936 | if (_instanceOptions.scrollbars) {
937 | for (axis in _scrollableAxes) {
938 | if (_scrollableAxes.hasOwnProperty(axis)) {
939 | _scrollbarNodes[axis].className = _scrollbarNodes[axis].className.replace(/ ?active/g, '');
940 | }
941 | }
942 | }
943 |
944 | // Store final position if scrolling occurred
945 | _baseScrollPosition.x = _lastScrollPosition.x;
946 | _baseScrollPosition.y = _lastScrollPosition.y;
947 |
948 | scrollEvent = _getPosition();
949 |
950 | if (!scrollCancelled) {
951 | _fireEvent('scroll', scrollEvent);
952 | _updateSegments(true);
953 | }
954 |
955 | // Always fire the scroll end event, including an argument indicating whether
956 | // the scroll was cancelled
957 | scrollEvent.cancelled = scrollCancelled;
958 | _fireEvent('scrollend', scrollEvent);
959 |
960 | // Restore transitions
961 | for (axis in _scrollableAxes) {
962 | if (_scrollableAxes.hasOwnProperty(axis)) {
963 | _scrollNodes[axis].style[_transitionProperty] = '';
964 | if (_instanceOptions.scrollbars) {
965 | _scrollbarNodes[axis].style[_transitionProperty] = '';
966 | }
967 | }
968 | }
969 |
970 | // Clear any remaining timeouts
971 | for (i = 0, l = _timeouts.length; i < l; i = i + 1) {
972 | window.clearTimeout(_timeouts[i]);
973 | }
974 | _timeouts.length = 0;
975 | };
976 |
977 | /**
978 | * Interrupt a current scroll, allowing a start scroll during animation to trigger a new scroll
979 | */
980 | _interruptScroll = function _interruptScroll() {
981 | var axis, i, l;
982 |
983 | _isAnimating = false;
984 |
985 | // Update the stored base position
986 | _updateElementPosition();
987 |
988 | // Ensure the parsed positions are set, also clearing transitions
989 | for (axis in _scrollableAxes) {
990 | if (_scrollableAxes.hasOwnProperty(axis)) {
991 | _setAxisPosition(axis, _baseScrollPosition[axis], 16, _instanceOptions.bounceDecelerationBezier);
992 | }
993 | }
994 |
995 | // Update segment tracking if snapping is active
996 | _updateSegments(false);
997 |
998 | // Clear any remaining timeouts
999 | for (i = 0, l = _timeouts.length; i < l; i = i + 1) {
1000 | window.clearTimeout(_timeouts[i]);
1001 | }
1002 | _timeouts.length = 0;
1003 | };
1004 |
1005 | /**
1006 | * Determine whether a scroll fling or bounceback is required, and set up the styles and
1007 | * timeouts required.
1008 | */
1009 | _flingScroll = function _flingScroll() {
1010 | var i, axis, movementTime, movementSpeed, lastPosition, comparisonPosition, flingDuration, flingDistance, flingPosition, bounceDelay, bounceDistance, bounceDuration, bounceTarget, boundsBounce, modifiedDistance, flingBezier, timeProportion, boundsCrossDelay, flingStartSegment, beyondBoundsFlingDistance, baseFlingComponent;
1011 | var maxAnimationTime = 0;
1012 | var moveRequired = false;
1013 | var scrollPositionsToApply = {};
1014 |
1015 | // If we only have the start event available, or flinging is disabled,
1016 | // or the scroll was triggered by a scrollwheel, no action required.
1017 | if (_eventHistory.length === 1 || !_instanceOptions.flinging || _inputIdentifier === 'scrollwheel') {
1018 | return false;
1019 | }
1020 |
1021 | for (axis in _scrollableAxes) {
1022 | if (_scrollableAxes.hasOwnProperty(axis)) {
1023 | bounceDuration = 350;
1024 | bounceDistance = 0;
1025 | boundsBounce = false;
1026 | bounceTarget = false;
1027 | boundsCrossDelay = undefined;
1028 |
1029 | // Re-set a default bezier curve for the animation for potential modification
1030 | flingBezier = _instanceOptions.flingBezier;
1031 |
1032 | // Get the last movement speed, in pixels per millisecond. To do this, look at the events
1033 | // in the last 100ms and average out the speed, using a minimum number of two points.
1034 | lastPosition = _eventHistory[_eventHistory.length - 1];
1035 | comparisonPosition = _eventHistory[_eventHistory.length - 2];
1036 | for (i = _eventHistory.length - 3; i >= 0; i = i - 1) {
1037 | if (lastPosition.t - _eventHistory[i].t > 100) {
1038 | break;
1039 | }
1040 | comparisonPosition = _eventHistory[i];
1041 | }
1042 |
1043 | // Get the last movement time. If this is zero - as can happen with
1044 | // some scrollwheel events on some platforms - increase it to 16ms as
1045 | // if the movement occurred over a single frame at 60fps.
1046 | movementTime = lastPosition.t - comparisonPosition.t;
1047 | if (!movementTime) {
1048 | movementTime = 16;
1049 | }
1050 |
1051 | // Derive the movement speed
1052 | movementSpeed = (lastPosition[axis] - comparisonPosition[axis]) / movementTime;
1053 |
1054 | // If there is little speed, no further action required except for a bounceback, below.
1055 | if (Math.abs(movementSpeed) < _kMinimumSpeed) {
1056 | flingDuration = 0;
1057 | flingDistance = 0;
1058 |
1059 | } else {
1060 |
1061 |
1062 | /* Calculate the fling duration. As per TouchScroll, the speed at any particular
1063 | point in time can be calculated as:
1064 | { speed } = { initial speed } * ({ friction } to the power of { duration })
1065 | ...assuming all values are in equal pixels/millisecond measurements. As we know the
1066 | minimum target speed, this can be altered to:
1067 | { duration } = log( { speed } / { initial speed } ) / log( { friction } )
1068 | */
1069 |
1070 | flingDuration = Math.log(_kMinimumSpeed / Math.abs(movementSpeed)) / Math.log(_kFriction);
1071 |
1072 |
1073 | /* Calculate the fling distance (before any bouncing or snapping). As per
1074 | TouchScroll, the total distance covered can be approximated by summing
1075 | the distance per millisecond, per millisecond of duration - a divergent series,
1076 | and so rather tricky to model otherwise!
1077 | So using values in pixels per millisecond:
1078 | { distance } = { initial speed } * (1 - ({ friction } to the power
1079 | of { duration + 1 }) / (1 - { friction })
1080 | */
1081 |
1082 | flingDistance = movementSpeed * (1 - Math.pow(_kFriction, flingDuration + 1)) / (1 - _kFriction);
1083 | }
1084 |
1085 | // Determine a target fling position
1086 | flingPosition = Math.floor(_lastScrollPosition[axis] + flingDistance);
1087 |
1088 | // If bouncing is disabled, and the last scroll position and fling position are both at a bound,
1089 | // reset the fling position to the bound
1090 | if (!_instanceOptions.bouncing) {
1091 | if (_lastScrollPosition[axis] === 0 && flingPosition > 0) {
1092 | flingPosition = 0;
1093 | } else if (_lastScrollPosition[axis] === _metrics.scrollEnd[axis] && flingPosition < _lastScrollPosition[axis]) {
1094 | flingPosition = _lastScrollPosition[axis];
1095 | }
1096 | }
1097 |
1098 | // In single-page-scroll mode, determine the page to snap to - maximum one page
1099 | // in either direction from the *start* page.
1100 | if (_instanceOptions.singlePageScrolls && _instanceOptions.snapping) {
1101 | flingStartSegment = -_lastScrollPosition[axis] / _snapGridSize[axis];
1102 | if (_baseSegment[axis] < flingStartSegment) {
1103 | flingStartSegment = Math.floor(flingStartSegment);
1104 | } else {
1105 | flingStartSegment = Math.ceil(flingStartSegment);
1106 | }
1107 |
1108 | // If the target position will end up beyond another page, target that page edge
1109 | if (flingPosition > -(_baseSegment[axis] - 1) * _snapGridSize[axis]) {
1110 | bounceDistance = flingPosition + (_baseSegment[axis] - 1) * _snapGridSize[axis];
1111 | } else if (flingPosition < -(_baseSegment[axis] + 1) * _snapGridSize[axis]) {
1112 | bounceDistance = flingPosition + (_baseSegment[axis] + 1) * _snapGridSize[axis];
1113 |
1114 | // Otherwise, if the movement speed was above the minimum velocity, continue
1115 | // in the move direction.
1116 | } else if (Math.abs(movementSpeed) > _kMinimumSpeed) {
1117 |
1118 | // Determine the target segment
1119 | if (movementSpeed < 0) {
1120 | flingPosition = Math.floor(_lastScrollPosition[axis] / _snapGridSize[axis]) * _snapGridSize[axis];
1121 | } else {
1122 | flingPosition = Math.ceil(_lastScrollPosition[axis] / _snapGridSize[axis]) * _snapGridSize[axis];
1123 | }
1124 |
1125 | flingDuration = Math.min(_instanceOptions.maxFlingDuration, flingDuration * (flingPosition - _lastScrollPosition[axis]) / flingDistance);
1126 | }
1127 |
1128 | // In non-paginated snapping mode, snap to the nearest grid location to the target
1129 | } else if (_instanceOptions.snapping) {
1130 | bounceDistance = flingPosition - (Math.round(flingPosition / _snapGridSize[axis]) * _snapGridSize[axis]);
1131 | }
1132 |
1133 | // Deal with cases where the target is beyond the bounds
1134 | if (flingPosition - bounceDistance > 0) {
1135 | bounceDistance = flingPosition;
1136 | boundsBounce = true;
1137 | } else if (flingPosition - bounceDistance < _metrics.scrollEnd[axis]) {
1138 | bounceDistance = flingPosition - _metrics.scrollEnd[axis];
1139 | boundsBounce = true;
1140 | }
1141 |
1142 | // Amend the positions and bezier curve if necessary
1143 | if (bounceDistance) {
1144 |
1145 | // If the fling moves the scroller beyond the normal scroll bounds, and
1146 | // the bounce is snapping the scroll back after the fling:
1147 | if (boundsBounce && _instanceOptions.bouncing && flingDistance) {
1148 | flingDistance = Math.floor(flingDistance);
1149 |
1150 | if (flingPosition > 0) {
1151 | beyondBoundsFlingDistance = flingPosition - Math.max(0, _lastScrollPosition[axis]);
1152 | } else {
1153 | beyondBoundsFlingDistance = flingPosition - Math.min(_metrics.scrollEnd[axis], _lastScrollPosition[axis]);
1154 | }
1155 | baseFlingComponent = flingDistance - beyondBoundsFlingDistance;
1156 |
1157 | // Determine the time proportion the original bound is along the fling curve
1158 | if (!flingDistance || !flingDuration) {
1159 | timeProportion = 0;
1160 | } else {
1161 | timeProportion = flingBezier._getCoordinateForT(flingBezier.getTForY((flingDistance - beyondBoundsFlingDistance) / flingDistance, 1 / flingDuration), flingBezier._p1.x, flingBezier._p2.x);
1162 | boundsCrossDelay = timeProportion * flingDuration;
1163 | }
1164 |
1165 | // Eighth the distance beyonds the bounds
1166 | modifiedDistance = Math.ceil(beyondBoundsFlingDistance / 8);
1167 |
1168 | // Further limit the bounce to half the container dimensions
1169 | if (Math.abs(modifiedDistance) > _metrics.container[axis] / 2) {
1170 | if (modifiedDistance < 0) {
1171 | modifiedDistance = -Math.floor(_metrics.container[axis] / 2);
1172 | } else {
1173 | modifiedDistance = Math.floor(_metrics.container[axis] / 2);
1174 | }
1175 | }
1176 |
1177 | if (flingPosition > 0) {
1178 | bounceTarget = 0;
1179 | } else {
1180 | bounceTarget = _metrics.scrollEnd[axis];
1181 | }
1182 |
1183 | // If the entire fling is a bounce, modify appropriately
1184 | if (timeProportion === 0) {
1185 | flingDuration = flingDuration / 6;
1186 | flingPosition = _lastScrollPosition[axis] + baseFlingComponent + modifiedDistance;
1187 | bounceDelay = flingDuration;
1188 |
1189 | // Otherwise, take a new curve and add it to the timeout stack for the bounce
1190 | } else {
1191 |
1192 | // The new bounce delay is the pre-boundary fling duration, plus a
1193 | // sixth of the post-boundary fling.
1194 | bounceDelay = (timeProportion + ((1 - timeProportion) / 6)) * flingDuration;
1195 |
1196 | _scheduleAxisPosition(axis, (_lastScrollPosition[axis] + baseFlingComponent + modifiedDistance), ((1 - timeProportion) * flingDuration / 6), _instanceOptions.bounceDecelerationBezier, boundsCrossDelay);
1197 |
1198 | // Modify the fling to match, clipping to prevent over-fling
1199 | flingBezier = flingBezier.divideAtX(bounceDelay / flingDuration, 1 / flingDuration)[0];
1200 | flingDuration = bounceDelay;
1201 | flingPosition = (_lastScrollPosition[axis] + baseFlingComponent + modifiedDistance);
1202 | }
1203 |
1204 | // If the fling requires snapping to a snap location, and the bounce needs to
1205 | // reverse the fling direction after the fling completes:
1206 | } else if ((flingDistance < 0 && bounceDistance < flingDistance) || (flingDistance > 0 && bounceDistance > flingDistance)) {
1207 |
1208 | // Shorten the original fling duration to reflect the bounce
1209 | flingPosition = flingPosition - Math.floor(flingDistance / 2);
1210 | bounceDistance = bounceDistance - Math.floor(flingDistance / 2);
1211 | bounceDuration = Math.sqrt(Math.abs(bounceDistance)) * 50;
1212 | bounceTarget = flingPosition - bounceDistance;
1213 | flingDuration = 350;
1214 | bounceDelay = flingDuration * 0.97;
1215 |
1216 | // If the bounce is truncating the fling, or continuing the fling on in the same
1217 | // direction to hit the next boundary:
1218 | } else {
1219 | flingPosition = flingPosition - bounceDistance;
1220 |
1221 | // If there was no fling distance originally, use the bounce details
1222 | if (!flingDistance) {
1223 | flingDuration = bounceDuration;
1224 |
1225 | // If truncating the fling at a snapping edge:
1226 | } else if ((flingDistance < 0 && bounceDistance < 0) || (flingDistance > 0 && bounceDistance > 0)) {
1227 | timeProportion = flingBezier._getCoordinateForT(flingBezier.getTForY((Math.abs(flingDistance) - Math.abs(bounceDistance)) / Math.abs(flingDistance), 1 / flingDuration), flingBezier._p1.x, flingBezier._p2.x);
1228 | flingBezier = flingBezier.divideAtX(timeProportion, 1 / flingDuration)[0];
1229 | flingDuration = Math.round(flingDuration * timeProportion);
1230 |
1231 | // If extending the fling to reach the next snapping boundary, no further
1232 | // action is required.
1233 | }
1234 |
1235 | bounceDistance = 0;
1236 | bounceDuration = 0;
1237 | }
1238 | }
1239 |
1240 | // If no fling or bounce is required, continue
1241 | if (flingPosition === _lastScrollPosition[axis] && !bounceDistance) {
1242 | continue;
1243 | }
1244 | moveRequired = true;
1245 |
1246 | // Perform the fling
1247 | _setAxisPosition(axis, flingPosition, flingDuration, flingBezier, boundsCrossDelay);
1248 |
1249 | // Schedule a bounce if appropriate
1250 | if (bounceDistance && bounceDuration) {
1251 | _scheduleAxisPosition(axis, bounceTarget, bounceDuration, _instanceOptions.bounceBezier, bounceDelay);
1252 | }
1253 |
1254 | maxAnimationTime = Math.max(maxAnimationTime, bounceDistance ? (bounceDelay + bounceDuration) : flingDuration);
1255 | scrollPositionsToApply[axis] = (bounceTarget === false) ? flingPosition : bounceTarget;
1256 | }
1257 | }
1258 |
1259 | if (moveRequired && maxAnimationTime) {
1260 | _timeouts.push(setTimeout(function () {
1261 | var anAxis;
1262 |
1263 | // Update the stored scroll position ready for finalising
1264 | for (anAxis in scrollPositionsToApply) {
1265 | if (scrollPositionsToApply.hasOwnProperty(anAxis)) {
1266 | _lastScrollPosition[anAxis] = scrollPositionsToApply[anAxis];
1267 | }
1268 | }
1269 |
1270 | _finalizeScroll();
1271 | }, maxAnimationTime));
1272 | }
1273 |
1274 | return moveRequired;
1275 | };
1276 |
1277 | /**
1278 | * Bounce back into bounds if necessary, or snap to a grid location.
1279 | */
1280 | _snapScroll = function _snapScroll(scrollCancelled) {
1281 | var axis;
1282 | var snapDuration = scrollCancelled ? 100 : 350;
1283 | var targetPosition = _lastScrollPosition;
1284 |
1285 | // Get the current position and see if a snap is required
1286 | if (_instanceOptions.snapping) {
1287 |
1288 | // Store current snap index
1289 | _snapIndex = _getSnapIndexForPosition(targetPosition);
1290 | targetPosition = _getSnapPositionForIndexes(_snapIndex, targetPosition);
1291 | }
1292 | targetPosition = _limitToBounds(targetPosition);
1293 |
1294 | var snapRequired = false;
1295 | for (axis in _baseScrollableAxes) {
1296 | if (_baseScrollableAxes.hasOwnProperty(axis)) {
1297 | if (targetPosition[axis] !== _lastScrollPosition[axis]) {
1298 | snapRequired = true;
1299 | }
1300 | }
1301 | }
1302 | if (!snapRequired) {
1303 | return false;
1304 | }
1305 |
1306 | // Perform the snap
1307 | for (axis in _baseScrollableAxes) {
1308 | if (_baseScrollableAxes.hasOwnProperty(axis)) {
1309 | _setAxisPosition(axis, targetPosition[axis], snapDuration);
1310 | }
1311 | }
1312 |
1313 | _timeouts.push(setTimeout(function () {
1314 |
1315 | // Update the stored scroll position ready for finalizing
1316 | _lastScrollPosition = targetPosition;
1317 |
1318 | _finalizeScroll(scrollCancelled);
1319 | }, snapDuration));
1320 |
1321 | return true;
1322 | };
1323 |
1324 | /**
1325 | * Get an appropriate snap index for a supplied point.
1326 | */
1327 | _getSnapIndexForPosition = function _getSnapIndexForPosition(coordinates) {
1328 | var axis;
1329 | var indexes = {x: 0, y: 0};
1330 | for (axis in _scrollableAxes) {
1331 | if (_scrollableAxes.hasOwnProperty(axis) && _snapGridSize[axis]) {
1332 | indexes[axis] = Math.round(coordinates[axis] / _snapGridSize[axis]);
1333 | }
1334 | }
1335 | return indexes;
1336 | };
1337 |
1338 | /**
1339 | * Get an appropriate snap point for a supplied index.
1340 | */
1341 | _getSnapPositionForIndexes = function _getSnapPositionForIndexes(indexes, currentCoordinates) {
1342 | var axis;
1343 | var coordinatesToReturn = {
1344 | x: currentCoordinates.x,
1345 | y: currentCoordinates.y
1346 | };
1347 | for (axis in _scrollableAxes) {
1348 | if (_scrollableAxes.hasOwnProperty(axis)) {
1349 | coordinatesToReturn[axis] = indexes[axis] * _snapGridSize[axis];
1350 | }
1351 | }
1352 | return coordinatesToReturn;
1353 | };
1354 |
1355 | /**
1356 | * Update the scroll position while scrolling is active, checking the position
1357 | * within bounds and rubberbanding/constraining as appropriate; also triggers a
1358 | * scroll position render if a requestAnimationFrame loop isn't active
1359 | */
1360 | _constrainAndRenderTargetScrollPosition = function _constrainAndRenderTargetScrollPosition() {
1361 | var axis, upperBound, lowerBound;
1362 |
1363 | // Update axes target positions if beyond bounds
1364 | for (axis in _scrollableAxes) {
1365 | if (_scrollableAxes.hasOwnProperty(axis)) {
1366 |
1367 | // Set bounds to the left and right of the container
1368 | upperBound = 0;
1369 | lowerBound = _metrics.scrollEnd[axis];
1370 |
1371 | if (_instanceOptions.singlePageScrolls && _instanceOptions.snapping) {
1372 |
1373 | // For a single-page-scroll, set the bounds to the left and right of the
1374 | // current segment
1375 | upperBound = Math.min(upperBound, -(_baseSegment[axis] - 1) * _snapGridSize[axis]);
1376 | lowerBound = Math.max(lowerBound, -(_baseSegment[axis] + 1) * _snapGridSize[axis]);
1377 | }
1378 |
1379 | if (_targetScrollPosition[axis] > upperBound) {
1380 | _targetScrollPosition[axis] = upperBound + _modifyDistanceBeyondBounds(_targetScrollPosition[axis] - upperBound, axis);
1381 | } else if (_targetScrollPosition[axis] < lowerBound) {
1382 | _targetScrollPosition[axis] = lowerBound + _modifyDistanceBeyondBounds(_targetScrollPosition[axis] - lowerBound, axis);
1383 | }
1384 | }
1385 | }
1386 |
1387 | // Trigger a scroll position update for platforms not using requestAnimationFrames
1388 | if (!_reqAnimationFrame) {
1389 | _scheduleRender();
1390 | }
1391 | };
1392 |
1393 | /**
1394 | * Limit coordinates within the bounds of the scrollable viewport.
1395 | */
1396 | _limitToBounds = function _limitToBounds(coordinates) {
1397 | var axis;
1398 | var coordinatesToReturn = { x: coordinates.x, y: coordinates.y };
1399 |
1400 | for (axis in _scrollableAxes) {
1401 | if (_scrollableAxes.hasOwnProperty(axis)) {
1402 |
1403 | // If the coordinate is beyond the edges of the scroller, use the closest edge
1404 | if (coordinates[axis] > 0) {
1405 | coordinatesToReturn[axis] = 0;
1406 | continue;
1407 | }
1408 | if (coordinates[axis] < _metrics.scrollEnd[axis]) {
1409 | coordinatesToReturn[axis] = _metrics.scrollEnd[axis];
1410 | continue;
1411 | }
1412 | }
1413 | }
1414 |
1415 | return coordinatesToReturn;
1416 | };
1417 |
1418 |
1419 | /**
1420 | * Sets up the DOM around the node to be scrolled.
1421 | */
1422 | _initializeDOM = function _initializeDOM() {
1423 | var offscreenFragment, offscreenNode, scrollYParent;
1424 |
1425 | // Check whether the DOM is already present and valid - if so, no further action required.
1426 | if (_existingDOMValid()) {
1427 | return;
1428 | }
1429 |
1430 | // Otherwise, the DOM needs to be created inside the originally supplied node. The node
1431 | // has a container inserted inside it - which acts as an anchor element with constraints -
1432 | // and then the scrollable layers as appropriate.
1433 |
1434 | // Create a new document fragment to temporarily hold the scrollable content
1435 | offscreenFragment = _scrollableMasterNode.ownerDocument.createDocumentFragment();
1436 | offscreenNode = document.createElement('DIV');
1437 | offscreenFragment.appendChild(offscreenNode);
1438 |
1439 | // Drop in the wrapping HTML
1440 | offscreenNode.innerHTML = FTScroller.prototype.getPrependedHTML(!_instanceOptions.scrollingX, !_instanceOptions.scrollingY, _instanceOptions.hwAccelerationClass) + FTScroller.prototype.getAppendedHTML(!_instanceOptions.scrollingX, !_instanceOptions.scrollingY, _instanceOptions.hwAccelerationClass, _instanceOptions.scrollbars);
1441 |
1442 | // Update references as appropriate
1443 | _containerNode = offscreenNode.firstElementChild;
1444 | scrollYParent = _containerNode;
1445 | if (_instanceOptions.scrollingX) {
1446 | _scrollNodes.x = _containerNode.firstElementChild;
1447 | scrollYParent = _scrollNodes.x;
1448 | if (_instanceOptions.scrollbars) {
1449 | _scrollbarNodes.x = _containerNode.getElementsByClassName('ftscroller_scrollbarx')[0];
1450 | }
1451 | }
1452 | if (_instanceOptions.scrollingY) {
1453 | _scrollNodes.y = scrollYParent.firstElementChild;
1454 | if (_instanceOptions.scrollbars) {
1455 | _scrollbarNodes.y = _containerNode.getElementsByClassName('ftscroller_scrollbary')[0];
1456 | }
1457 | _contentParentNode = _scrollNodes.y;
1458 | } else {
1459 | _contentParentNode = _scrollNodes.x;
1460 | }
1461 |
1462 | // Take the contents of the scrollable element, and copy them into the new container
1463 | while (_scrollableMasterNode.firstChild) {
1464 | _contentParentNode.appendChild(_scrollableMasterNode.firstChild);
1465 | }
1466 |
1467 | // Move the wrapped elements back into the document
1468 | _scrollableMasterNode.appendChild(_containerNode);
1469 | };
1470 |
1471 | /**
1472 | * Attempts to use any existing DOM scroller nodes if possible, returning true if so;
1473 | * updates all internal element references.
1474 | */
1475 | _existingDOMValid = function _existingDOMValid() {
1476 | var scrollerContainer, layerX, layerY, yParent, scrollerX, scrollerY, candidates, i, l;
1477 |
1478 | // Check that there's an initial child node, and make sure it's the container class
1479 | scrollerContainer = _scrollableMasterNode.firstElementChild;
1480 | if (!scrollerContainer || scrollerContainer.className.indexOf('ftscroller_container') === -1) {
1481 | return;
1482 | }
1483 |
1484 | // If x-axis scrolling is enabled, find and verify the x scroller layer
1485 | if (_instanceOptions.scrollingX) {
1486 |
1487 | // Find and verify the x scroller layer
1488 | layerX = scrollerContainer.firstElementChild;
1489 | if (!layerX || layerX.className.indexOf('ftscroller_x') === -1) {
1490 | return;
1491 | }
1492 | yParent = layerX;
1493 |
1494 | // Find and verify the x scrollbar if enabled
1495 | if (_instanceOptions.scrollbars) {
1496 | candidates = scrollerContainer.getElementsByClassName('ftscroller_scrollbarx');
1497 | if (candidates) {
1498 | for (i = 0, l = candidates.length; i < l; i = i + 1) {
1499 | if (candidates[i].parentNode === scrollerContainer) {
1500 | scrollerX = candidates[i];
1501 | break;
1502 | }
1503 | }
1504 | }
1505 | if (!scrollerX) {
1506 | return;
1507 | }
1508 | }
1509 | } else {
1510 | yParent = scrollerContainer;
1511 | }
1512 |
1513 | // If y-axis scrolling is enabled, find and verify the y scroller layer
1514 | if (_instanceOptions.scrollingY) {
1515 |
1516 | // Find and verify the x scroller layer
1517 | layerY = yParent.firstElementChild;
1518 | if (!layerY || layerY.className.indexOf('ftscroller_y') === -1) {
1519 | return;
1520 | }
1521 |
1522 | // Find and verify the y scrollbar if enabled
1523 | if (_instanceOptions.scrollbars) {
1524 | candidates = scrollerContainer.getElementsByClassName('ftscroller_scrollbary');
1525 | if (candidates) {
1526 | for (i = 0, l = candidates.length; i < l; i = i + 1) {
1527 | if (candidates[i].parentNode === scrollerContainer) {
1528 | scrollerY = candidates[i];
1529 | break;
1530 | }
1531 | }
1532 | }
1533 | if (!scrollerY) {
1534 | return;
1535 | }
1536 | }
1537 | }
1538 |
1539 | // Elements found and verified - update the references and return success
1540 | _containerNode = scrollerContainer;
1541 | if (layerX) {
1542 | _scrollNodes.x = layerX;
1543 | }
1544 | if (layerY) {
1545 | _scrollNodes.y = layerY;
1546 | }
1547 | if (scrollerX) {
1548 | _scrollbarNodes.x = scrollerX;
1549 | }
1550 | if (scrollerY) {
1551 | _scrollbarNodes.y = scrollerY;
1552 | }
1553 | if (_instanceOptions.scrollingY) {
1554 | _contentParentNode = layerY;
1555 | } else {
1556 | _contentParentNode = layerX;
1557 | }
1558 | return true;
1559 | };
1560 |
1561 | _domChanged = function _domChanged(e) {
1562 |
1563 | // If the timer is active, clear it
1564 | if (_domChangeDebouncer) {
1565 | window.clearTimeout(_domChangeDebouncer);
1566 | }
1567 |
1568 | // React to resizes at once
1569 | if (e && e.type === 'resize') {
1570 | _updateDimensions();
1571 |
1572 | // For other changes, which may occur in groups, set up the DOM changed timer
1573 | } else {
1574 | _domChangeDebouncer = setTimeout(function () {
1575 | _updateDimensions();
1576 | }, 100);
1577 | }
1578 | };
1579 |
1580 | _updateDimensions = function _updateDimensions(ignoreSnapScroll) {
1581 | var axis;
1582 |
1583 | // Only update dimensions if the container node exists (DOM elements can go away if
1584 | // the scroller instance is not destroyed correctly)
1585 | if (!_containerNode || !_contentParentNode) {
1586 | return false;
1587 | }
1588 |
1589 | if (_domChangeDebouncer) {
1590 | window.clearTimeout(_domChangeDebouncer);
1591 | _domChangeDebouncer = false;
1592 | }
1593 | var containerWidth, containerHeight, startAlignments;
1594 |
1595 | // Calculate the starting alignment for comparison later
1596 | startAlignments = { x: false, y: false };
1597 | for (axis in startAlignments) {
1598 | if (startAlignments.hasOwnProperty(axis)) {
1599 | if (_lastScrollPosition[axis] === 0) {
1600 | startAlignments[axis] = -1;
1601 | } else if (_lastScrollPosition[axis] <= _metrics.scrollEnd[axis]) {
1602 | startAlignments[axis] = 1;
1603 | } else if (_lastScrollPosition[axis] * 2 <= _metrics.scrollEnd[axis] + 5 && _lastScrollPosition[axis] * 2 >= _metrics.scrollEnd[axis] - 5) {
1604 | startAlignments[axis] = 0;
1605 | }
1606 | }
1607 | }
1608 |
1609 | containerWidth = _containerNode.offsetWidth;
1610 | containerHeight = _containerNode.offsetHeight;
1611 |
1612 | // Grab the dimensions
1613 | var rawScrollWidth = options.contentWidth || _contentParentNode.offsetWidth;
1614 | var rawScrollHeight = options.contentHeight || _contentParentNode.offsetHeight;
1615 | var scrollWidth = rawScrollWidth;
1616 | var scrollHeight = rawScrollHeight;
1617 | var targetPosition = { x: _lastScrollPosition.x, y: _lastScrollPosition.y };
1618 |
1619 | // Update snap grid
1620 | if (!_snapGridSize.userX) {
1621 | _snapGridSize.x = containerWidth;
1622 | }
1623 | if (!_snapGridSize.userY) {
1624 | _snapGridSize.y = containerHeight;
1625 | }
1626 |
1627 | // If there is a grid, conform to the grid
1628 | if (_instanceOptions.snapping) {
1629 | if (_snapGridSize.userX) {
1630 | scrollWidth = Math.ceil(scrollWidth / _snapGridSize.userX) * _snapGridSize.userX;
1631 | } else {
1632 | scrollWidth = Math.ceil(scrollWidth / _snapGridSize.x) * _snapGridSize.x;
1633 | }
1634 | if (_snapGridSize.userY) {
1635 | scrollHeight = Math.ceil(scrollHeight / _snapGridSize.userY) * _snapGridSize.userY;
1636 | } else {
1637 | scrollHeight = Math.ceil(scrollHeight / _snapGridSize.y) * _snapGridSize.y;
1638 | }
1639 | }
1640 |
1641 | // If no details have changed, return.
1642 | if (_metrics.container.x === containerWidth && _metrics.container.y === containerHeight && _metrics.content.x === scrollWidth && _metrics.content.y === scrollHeight) {
1643 | return;
1644 | }
1645 |
1646 | // Update the sizes
1647 | _metrics.container.x = containerWidth;
1648 | _metrics.container.y = containerHeight;
1649 | _metrics.content.x = scrollWidth;
1650 | _metrics.content.rawX = rawScrollWidth;
1651 | _metrics.content.y = scrollHeight;
1652 | _metrics.content.rawY = rawScrollHeight;
1653 | _metrics.scrollEnd.x = containerWidth - scrollWidth;
1654 | _metrics.scrollEnd.y = containerHeight - scrollHeight;
1655 |
1656 | _updateScrollbarDimensions();
1657 |
1658 | // If scrolling is in progress, trigger a scroll update
1659 | if (_isScrolling) {
1660 | _lastScrollPosition.x--;
1661 | _lastScrollPosition.y--;
1662 | _constrainAndRenderTargetScrollPosition();
1663 |
1664 | // If scrolling *isn't* in progress, snap and realign.
1665 | } else {
1666 | if (!ignoreSnapScroll && _instanceOptions.snapping) {
1667 |
1668 | // Ensure bounds are correct
1669 | _updateSegments();
1670 | targetPosition = _getSnapPositionForIndexes(_snapIndex, _lastScrollPosition);
1671 | }
1672 |
1673 | // Apply base alignment if appropriate
1674 | for (axis in targetPosition) {
1675 | if (targetPosition.hasOwnProperty(axis)) {
1676 |
1677 | // If the container is smaller than the content, determine whether to apply the
1678 | // alignment. This occurs if a scroll has never taken place, or if the position
1679 | // was previously at the correct "end" and can be maintained.
1680 | if (_metrics.container[axis] < _metrics.content[axis]) {
1681 | if (_hasBeenScrolled && _instanceOptions.baseAlignments[axis] !== startAlignments[axis]) {
1682 | continue;
1683 | }
1684 | }
1685 |
1686 | // Apply the alignment
1687 | if (_instanceOptions.baseAlignments[axis] === 1) {
1688 | targetPosition[axis] = _metrics.scrollEnd[axis];
1689 | } else if (_instanceOptions.baseAlignments[axis] === 0) {
1690 | targetPosition[axis] = Math.floor(_metrics.scrollEnd[axis] / 2);
1691 | } else if (_instanceOptions.baseAlignments[axis] === -1) {
1692 | targetPosition[axis] = 0;
1693 | }
1694 | }
1695 | }
1696 |
1697 | // Limit to bounds
1698 | targetPosition = _limitToBounds(targetPosition);
1699 |
1700 | if (_instanceOptions.scrollingX && targetPosition.x !== _lastScrollPosition.x) {
1701 | _setAxisPosition('x', targetPosition.x, 0);
1702 | _baseScrollPosition.x = targetPosition.x;
1703 | }
1704 | if (_instanceOptions.scrollingY && targetPosition.y !== _lastScrollPosition.y) {
1705 | _setAxisPosition('y', targetPosition.y, 0);
1706 | _baseScrollPosition.y = targetPosition.y;
1707 | }
1708 | }
1709 | };
1710 |
1711 | _updateScrollbarDimensions = function _updateScrollbarDimensions() {
1712 |
1713 | // Update scrollbar sizes
1714 | if (_instanceOptions.scrollbars) {
1715 | if (_instanceOptions.scrollingX) {
1716 | _scrollbarNodes.x.style.width = Math.max(6, Math.round(_metrics.container.x * (_metrics.container.x / _metrics.content.x) - 4)) + 'px';
1717 | }
1718 | if (_instanceOptions.scrollingY) {
1719 | _scrollbarNodes.y.style.height = Math.max(6, Math.round(_metrics.container.y * (_metrics.container.y / _metrics.content.y) - 4)) + 'px';
1720 | }
1721 | }
1722 |
1723 | // Update scroll caches
1724 | _scrollableAxes = {};
1725 | if (_instanceOptions.scrollingX && (_metrics.content.x > _metrics.container.x || _instanceOptions.alwaysScroll)) {
1726 | _scrollableAxes.x = true;
1727 | }
1728 | if (_instanceOptions.scrollingY && (_metrics.content.y > _metrics.container.y || _instanceOptions.alwaysScroll)) {
1729 | _scrollableAxes.y = true;
1730 | }
1731 | };
1732 |
1733 | _updateElementPosition = function _updateElementPosition() {
1734 | var axis, computedStyle, splitStyle;
1735 |
1736 | // Retrieve the current position of each active axis.
1737 | // Custom parsing is used instead of native matrix support for speed and for
1738 | // backwards compatibility.
1739 | for (axis in _scrollableAxes) {
1740 | if (_scrollableAxes.hasOwnProperty(axis)) {
1741 | computedStyle = window.getComputedStyle(_scrollNodes[axis], null)[_vendorTransformLookup];
1742 | splitStyle = computedStyle.split(', ');
1743 |
1744 | // For 2d-style transforms, pull out elements four or five
1745 | if (splitStyle.length === 6) {
1746 | _baseScrollPosition[axis] = parseInt(splitStyle[(axis === 'y') ? 5 : 4], 10);
1747 |
1748 | // For 3d-style transforms, pull out elements twelve or thirteen
1749 | } else {
1750 | _baseScrollPosition[axis] = parseInt(splitStyle[(axis === 'y') ? 13 : 12], 10);
1751 | }
1752 | _lastScrollPosition[axis] = _baseScrollPosition[axis];
1753 | }
1754 | }
1755 | };
1756 |
1757 | _updateSegments = function _updateSegments(scrollFinalised) {
1758 | var axis;
1759 | var newSegment = { x: 0, y: 0 };
1760 |
1761 | // If snapping is disabled, return without any further action required
1762 | if (!_instanceOptions.snapping) {
1763 | return;
1764 | }
1765 |
1766 | // Calculate the new segments
1767 | for (axis in _scrollableAxes) {
1768 | if (_scrollableAxes.hasOwnProperty(axis)) {
1769 | newSegment[axis] = Math.max(0, Math.min(Math.ceil(_metrics.content[axis] / _snapGridSize[axis]) - 1, Math.round(-_lastScrollPosition[axis] / _snapGridSize[axis])));
1770 | }
1771 | }
1772 |
1773 | // In all cases update the active segment if appropriate
1774 | if (newSegment.x !== _activeSegment.x || newSegment.y !== _activeSegment.y) {
1775 | _activeSegment.x = newSegment.x;
1776 | _activeSegment.y = newSegment.y;
1777 | _fireEvent('segmentwillchange', { segmentX: newSegment.x, segmentY: newSegment.y });
1778 | }
1779 |
1780 | // If the scroll has been finalised, also update the base segment
1781 | if (scrollFinalised) {
1782 | if (newSegment.x !== _baseSegment.x || newSegment.y !== _baseSegment.y) {
1783 | _baseSegment.x = newSegment.x;
1784 | _baseSegment.y = newSegment.y;
1785 | _fireEvent('segmentdidchange', { segmentX: newSegment.x, segmentY: newSegment.y });
1786 | }
1787 | }
1788 | };
1789 |
1790 | _setAxisPosition = function _setAxisPosition(axis, position, animationDuration, animationBezier, boundsCrossDelay) {
1791 | var transitionCSSString, newPositionAtExtremity = null;
1792 |
1793 | // Only update position if the axis node exists (DOM elements can go away if
1794 | // the scroller instance is not destroyed correctly)
1795 | if (!_scrollNodes[axis]) {
1796 | return false;
1797 | }
1798 |
1799 | // Determine the transition property to apply to both the scroll element and the scrollbar
1800 | if (animationDuration) {
1801 | if (!animationBezier) {
1802 | animationBezier = _instanceOptions.flingBezier;
1803 | }
1804 |
1805 | transitionCSSString = _vendorCSSPrefix + 'transform ' + animationDuration + 'ms ' + animationBezier.toString();
1806 | } else {
1807 | transitionCSSString = '';
1808 | }
1809 |
1810 | // Apply the transition property to elements
1811 | _scrollNodes[axis].style[_transitionProperty] = transitionCSSString;
1812 | if (_instanceOptions.scrollbars) {
1813 | _scrollbarNodes[axis].style[_transitionProperty] = transitionCSSString;
1814 | }
1815 |
1816 | // Update the positions
1817 | _scrollNodes[axis].style[_transformProperty] = _translateRulePrefix + _transformPrefixes[axis] + position + 'px' + _transformSuffixes[axis];
1818 | if (_instanceOptions.scrollbars) {
1819 | _scrollbarNodes[axis].style[_transformProperty] = _translateRulePrefix + _transformPrefixes[axis] + (-position * _metrics.container[axis] / _metrics.content[axis]) + 'px' + _transformSuffixes[axis];
1820 | }
1821 |
1822 | // Determine whether the scroll is at an extremity.
1823 | if (position >= 0) {
1824 | newPositionAtExtremity = 'start';
1825 | } else if (position <= _metrics.scrollEnd[axis]) {
1826 | newPositionAtExtremity = 'end';
1827 | }
1828 |
1829 | // If the extremity status has changed, fire an appropriate event
1830 | if (newPositionAtExtremity !== _scrollAtExtremity[axis]) {
1831 | if (newPositionAtExtremity !== null) {
1832 | if (animationDuration) {
1833 | _timeouts.push(setTimeout(function() {
1834 | _fireEvent('reached' + newPositionAtExtremity, { axis: axis });
1835 | }, boundsCrossDelay || animationDuration));
1836 | } else {
1837 | _fireEvent('reached' + newPositionAtExtremity, { axis: axis });
1838 | }
1839 | }
1840 | _scrollAtExtremity[axis] = newPositionAtExtremity;
1841 | }
1842 |
1843 | // Update the recorded position if there's no duration
1844 | if (!animationDuration) {
1845 | _lastScrollPosition[axis] = position;
1846 | }
1847 | };
1848 |
1849 | /**
1850 | * Retrieve the current position as an object with scrollLeft and scrollTop
1851 | * properties.
1852 | */
1853 | _getPosition = function _getPosition() {
1854 | return {
1855 | scrollLeft: -_lastScrollPosition.x,
1856 | scrollTop: -_lastScrollPosition.y
1857 | };
1858 | };
1859 |
1860 | _scheduleAxisPosition = function _scheduleAxisPosition(axis, position, animationDuration, animationBezier, afterDelay) {
1861 | _timeouts.push(setTimeout(function () {
1862 | _setAxisPosition(axis, position, animationDuration, animationBezier);
1863 | }, afterDelay));
1864 | };
1865 |
1866 | _fireEvent = function _fireEvent(eventName, eventObject) {
1867 | var i, l;
1868 | eventObject.srcObject = _publicSelf;
1869 |
1870 | // Iterate through any listeners
1871 | for (i = 0, l = _eventListeners[eventName].length; i < l; i = i + 1) {
1872 |
1873 | // Execute each in a try/catch
1874 | try {
1875 | _eventListeners[eventName][i](eventObject);
1876 | } catch (error) {
1877 | if (window.console && window.console.error) {
1878 | if (error.message) {
1879 | window.console.error(error.message + ' (' + error.sourceURL + ', line ' + error.line + ')');
1880 | } else {
1881 | window.console.error('Error encountered executing FTScroller event listener callback for [' + eventName + ']. Add a "debugger" statement here to obtain a full backtrace.');
1882 | if (window.console.dir) window.console.dir(error);
1883 | }
1884 | }
1885 | }
1886 | }
1887 | };
1888 |
1889 | /**
1890 | * Update the scroll position so that the child element is in view.
1891 | */
1892 | _childFocused = function _childFocused(event) {
1893 | var offset, axis, visibleChildPortion;
1894 | var focusedNodeRect = _getBoundingRect(event.target);
1895 | var containerRect = _getBoundingRect(_containerNode);
1896 | var edgeMap = { x: 'left', y: 'top' };
1897 | var opEdgeMap = { x: 'right', y: 'bottom' };
1898 | var dimensionMap = { x: 'width', y: 'height' };
1899 |
1900 | // If an input is currently being tracked, ignore the focus event
1901 | if (_inputIdentifier !== false) {
1902 | return;
1903 | }
1904 |
1905 | for (axis in _scrollableAxes) {
1906 | if (_scrollableAxes.hasOwnProperty(axis)) {
1907 |
1908 | // If the focussed node is entirely in view, there is no need to center it
1909 | if (focusedNodeRect[edgeMap[axis]] >= containerRect[edgeMap[axis]] && focusedNodeRect[opEdgeMap[axis]] <= containerRect[opEdgeMap[axis]]) {
1910 | continue;
1911 | }
1912 |
1913 | // If the focussed node is larger than the container...
1914 | if (focusedNodeRect[dimensionMap[axis]] > containerRect[dimensionMap[axis]]) {
1915 |
1916 | visibleChildPortion = focusedNodeRect[dimensionMap[axis]] - Math.max(0, containerRect[edgeMap[axis]] - focusedNodeRect[edgeMap[axis]]) - Math.max(0, focusedNodeRect[opEdgeMap[axis]] - containerRect[opEdgeMap[axis]]);
1917 |
1918 | // If more than half a container's portion of the focussed node is visible, there's no need to center it
1919 | if (visibleChildPortion >= (containerRect[dimensionMap[axis]] / 2)) {
1920 | continue;
1921 | }
1922 | }
1923 |
1924 | // Set the target offset to be in the middle of the container, or as close as bounds permit
1925 | offset = -Math.round((focusedNodeRect[dimensionMap[axis]] / 2) - _lastScrollPosition[axis] + focusedNodeRect[edgeMap[axis]] - containerRect[edgeMap[axis]] - (containerRect[dimensionMap[axis]] / 2));
1926 | offset = Math.min(0, Math.max(_metrics.scrollEnd[axis], offset));
1927 |
1928 | // Perform the scroll
1929 | _setAxisPosition(axis, offset, 0);
1930 | _baseScrollPosition[axis] = offset;
1931 | }
1932 | }
1933 |
1934 | _fireEvent('scroll', _getPosition());
1935 | };
1936 |
1937 | /**
1938 | * Given a relative distance beyond the element bounds, returns a modified version to
1939 | * simulate bouncy/springy edges.
1940 | */
1941 | _modifyDistanceBeyondBounds = function _modifyDistanceBeyondBounds(distance, axis) {
1942 | if (!_instanceOptions.bouncing) {
1943 | return 0;
1944 | }
1945 | var e = Math.exp(distance / _metrics.container[axis]);
1946 | return Math.round(_metrics.container[axis] * 0.6 * (e - 1) / (e + 1));
1947 | };
1948 |
1949 | /**
1950 | * Given positions for each enabled axis, returns an object showing how far each axis is beyond
1951 | * bounds. If within bounds, -1 is returned; if at the bounds, 0 is returned.
1952 | */
1953 | _distancesBeyondBounds = function _distancesBeyondBounds(positions) {
1954 | var axis, position;
1955 | var distances = {};
1956 | for (axis in positions) {
1957 | if (positions.hasOwnProperty(axis)) {
1958 | position = positions[axis];
1959 |
1960 | // If the position is to the left/top, no further modification required
1961 | if (position >= 0) {
1962 | distances[axis] = position;
1963 |
1964 | // If it's within the bounds, use -1
1965 | } else if (position > _metrics.scrollEnd[axis]) {
1966 | distances[axis] = -1;
1967 |
1968 | // Otherwise, amend by the distance of the maximum edge
1969 | } else {
1970 | distances[axis] = _metrics.scrollEnd[axis] - position;
1971 | }
1972 | }
1973 | }
1974 | return distances;
1975 | };
1976 |
1977 | /**
1978 | * On platforms which support it, use RequestAnimationFrame to group
1979 | * position updates for speed. Starts the render process.
1980 | */
1981 | _startAnimation = function _startAnimation() {
1982 | if (_reqAnimationFrame) {
1983 | _cancelAnimation();
1984 | _animationFrameRequest = _reqAnimationFrame(_scheduleRender);
1985 | }
1986 | };
1987 |
1988 | /**
1989 | * On platforms which support RequestAnimationFrame, provide the rendering loop.
1990 | * Takes two arguments; the first is the render/position update function to
1991 | * be called, and the second is a string controlling the render type to
1992 | * allow previous changes to be cancelled - should be 'pan' or 'scroll'.
1993 | */
1994 | _scheduleRender = function _scheduleRender() {
1995 | var axis, positionUpdated;
1996 |
1997 | // If using requestAnimationFrame schedule the next update at once
1998 | if (_reqAnimationFrame) {
1999 | _animationFrameRequest = _reqAnimationFrame(_scheduleRender);
2000 | }
2001 |
2002 | // Perform the draw.
2003 | for (axis in _scrollableAxes) {
2004 | if (_scrollableAxes.hasOwnProperty(axis) && _targetScrollPosition[axis] !== _lastScrollPosition[axis]) {
2005 | _setAxisPosition(axis, _targetScrollPosition[axis]);
2006 | positionUpdated = true;
2007 | }
2008 | }
2009 |
2010 | // If full, locked scrolling has enabled, fire any scroll and segment change events
2011 | if (_isScrolling && positionUpdated) {
2012 | _fireEvent('scroll', _getPosition());
2013 | _updateSegments(false);
2014 | }
2015 | };
2016 |
2017 | /**
2018 | * Stops the animation process.
2019 | */
2020 | _cancelAnimation = function _cancelAnimation() {
2021 | if (_animationFrameRequest === false || !_cancelAnimationFrame) {
2022 | return;
2023 | }
2024 |
2025 | _cancelAnimationFrame(_animationFrameRequest);
2026 | _animationFrameRequest = false;
2027 | };
2028 |
2029 | /**
2030 | * Remove then re-set event handlers
2031 | */
2032 | _resetEventHandlers = function() {
2033 | _removeEventHandlers();
2034 | _addEventHandlers();
2035 | };
2036 |
2037 | /**
2038 | * Register event handlers
2039 | */
2040 | _addEventHandlers = function _addEventHandlers() {
2041 | var MutationObserver;
2042 |
2043 | // Only remove the event if the node exists (DOM elements can go away)
2044 | if (!_containerNode) {
2045 | return;
2046 | }
2047 |
2048 | if (_trackPointerEvents && !_instanceOptions.disabledInputMethods.pointer) {
2049 | if (_pointerEventsPrefixed) {
2050 | _containerNode.addEventListener('MSPointerDown', _onPointerDown);
2051 | _containerNode.addEventListener('MSPointerMove', _onPointerMove);
2052 | _containerNode.addEventListener('MSPointerUp', _onPointerUp);
2053 | _containerNode.addEventListener('MSPointerCancel', _onPointerCancel);
2054 | } else {
2055 | _containerNode.addEventListener('pointerdown', _onPointerDown);
2056 | _containerNode.addEventListener('pointermove', _onPointerMove);
2057 | _containerNode.addEventListener('pointerup', _onPointerUp);
2058 | _containerNode.addEventListener('pointercancel', _onPointerCancel);
2059 | }
2060 | } else {
2061 | if (_trackTouchEvents && !_instanceOptions.disabledInputMethods.touch) {
2062 | _containerNode.addEventListener('touchstart', _onTouchStart);
2063 | _containerNode.addEventListener('touchmove', _onTouchMove);
2064 | _containerNode.addEventListener('touchend', _onTouchEnd);
2065 | _containerNode.addEventListener('touchcancel', _onTouchEnd);
2066 | }
2067 | if (!_instanceOptions.disabledInputMethods.mouse) {
2068 | _containerNode.addEventListener('mousedown', _onMouseDown);
2069 | }
2070 | }
2071 | if (!_instanceOptions.disabledInputMethods.scroll) {
2072 | _containerNode.addEventListener('DOMMouseScroll', _onMouseScroll);
2073 | _containerNode.addEventListener('mousewheel', _onMouseScroll);
2074 | }
2075 |
2076 | // If any of the input methods which would eventually trigger a click are
2077 | // enabled, add a click event listener so that phantom clicks can be prevented
2078 | // at the end of a scroll. Otherwise, don't add a listener and don't prevent
2079 | // clicks.
2080 | if (!_instanceOptions.disabledInputMethods.mouse || !_instanceOptions.disabledInputMethods.touch || !_instanceOptions.disabledInputMethods.pointer) {
2081 |
2082 | // Add a click listener. On IE, add the listener to the document, to allow
2083 | // clicks to be cancelled if a scroll ends outside the bounds of the container; on
2084 | // other platforms, add to the container node.
2085 | if (_trackPointerEvents) {
2086 | document.addEventListener('click', _onClick, true);
2087 | } else {
2088 | _containerNode.addEventListener('click', _onClick, true);
2089 | }
2090 | }
2091 |
2092 | // Watch for changes inside the contained element to update bounds - de-bounced slightly.
2093 | if (!_instanceOptions.disabledInputMethods.focus) {
2094 | _contentParentNode.addEventListener('focus', _childFocused);
2095 | }
2096 | if (_instanceOptions.updateOnChanges) {
2097 |
2098 | // Try and reuse the old, disconnected observer instance if available
2099 | // Otherwise, check for support before proceeding
2100 | if (!_mutationObserver) {
2101 | MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window[_vendorStylePropertyPrefix + 'MutationObserver'];
2102 | if (MutationObserver) {
2103 | _mutationObserver = new MutationObserver(_domChanged);
2104 | }
2105 | }
2106 |
2107 | if (_mutationObserver) {
2108 | _mutationObserver.observe(_contentParentNode, {
2109 | childList: true,
2110 | characterData: true,
2111 | subtree: true
2112 | });
2113 | } else {
2114 | _contentParentNode.addEventListener('DOMSubtreeModified', function (e) {
2115 |
2116 | // Ignore changes to nested FT Scrollers - even updating a transform style
2117 | // can trigger a DOMSubtreeModified in IE, causing nested scrollers to always
2118 | // favour the deepest scroller as parent scrollers 'resize'/end scrolling.
2119 | if (e && (e.srcElement === _contentParentNode || e.srcElement.className.indexOf('ftscroller_') !== -1)) {
2120 | return;
2121 | }
2122 |
2123 | _domChanged();
2124 | }, true);
2125 | }
2126 | _contentParentNode.addEventListener('load', _domChanged);
2127 | }
2128 | if (_instanceOptions.updateOnWindowResize) {
2129 | window.addEventListener('resize', _domChanged);
2130 | }
2131 | };
2132 |
2133 | /**
2134 | * Remove event handlers.
2135 | *
2136 | * The current flags may not match the state when the event handlers were set up,
2137 | * so remove all event handlers unconditionally, just in case they're bound.
2138 | */
2139 | _removeEventHandlers = function _removeEventHandlers() {
2140 |
2141 | if (_containerNode) {
2142 | _containerNode.removeEventListener('MSPointerDown', _onPointerDown);
2143 | _containerNode.removeEventListener('MSPointerMove', _onPointerMove);
2144 | _containerNode.removeEventListener('MSPointerUp', _onPointerUp);
2145 | _containerNode.removeEventListener('MSPointerCancel', _onPointerCancel);
2146 | _containerNode.removeEventListener('pointerdown', _onPointerDown);
2147 | _containerNode.removeEventListener('pointermove', _onPointerMove);
2148 | _containerNode.removeEventListener('pointerup', _onPointerUp);
2149 | _containerNode.removeEventListener('pointercancel', _onPointerCancel);
2150 | _containerNode.removeEventListener('touchstart', _onTouchStart);
2151 | _containerNode.removeEventListener('touchmove', _onTouchMove);
2152 | _containerNode.removeEventListener('touchend', _onTouchEnd);
2153 | _containerNode.removeEventListener('touchcancel', _onTouchEnd);
2154 | _containerNode.removeEventListener('mousedown', _onMouseDown);
2155 | _containerNode.removeEventListener('DOMMouseScroll', _onMouseScroll);
2156 | _containerNode.removeEventListener('mousewheel', _onMouseScroll);
2157 | _containerNode.removeEventListener('click', _onClick, true);
2158 | }
2159 |
2160 | if (_contentParentNode) {
2161 | _contentParentNode.removeEventListener('focus', _childFocused);
2162 | _contentParentNode.removeEventListener('DOMSubtreeModified', _domChanged);
2163 | _contentParentNode.removeEventListener('load', _domChanged);
2164 | }
2165 |
2166 | if (_mutationObserver) {
2167 | _mutationObserver.disconnect();
2168 | }
2169 |
2170 | document.removeEventListener('mousemove', _onMouseMove);
2171 | document.removeEventListener('mouseup', _onMouseUp);
2172 | document.removeEventListener('click', _onClick, true);
2173 | window.removeEventListener('resize', _domChanged);
2174 | };
2175 |
2176 | /**
2177 | * Touch event handlers
2178 | */
2179 | _onTouchStart = function _onTouchStart(startEvent) {
2180 | var i, l, touchEvent;
2181 |
2182 | // If a touch is already active, ensure that the index
2183 | // is mapped to the correct finger, and return.
2184 | if (_inputIdentifier) {
2185 | for (i = 0, l = startEvent.touches.length; i < l; i = i + 1) {
2186 | if (startEvent.touches[i].identifier === _inputIdentifier) {
2187 | _inputIndex = i;
2188 | }
2189 | }
2190 | return;
2191 | }
2192 |
2193 | // Track the new touch's identifier, reset index, and pass
2194 | // the coordinates to the scroll start function.
2195 | touchEvent = startEvent.touches[0];
2196 | _inputIdentifier = touchEvent.identifier;
2197 | _inputIndex = 0;
2198 | _startScroll(touchEvent.clientX, touchEvent.clientY, startEvent.timeStamp, startEvent);
2199 | };
2200 | _onTouchMove = function _onTouchMove(moveEvent) {
2201 | if (_inputIdentifier === false) {
2202 | return;
2203 | }
2204 |
2205 | // Get the coordinates from the appropriate touch event and
2206 | // pass them on to the scroll handler
2207 | var touchEvent = moveEvent.touches[_inputIndex];
2208 | _updateScroll(touchEvent.clientX, touchEvent.clientY, moveEvent.timeStamp, moveEvent);
2209 | };
2210 | _onTouchEnd = function _onTouchEnd(endEvent) {
2211 | var i, l;
2212 |
2213 | // Check whether the original touch event is still active,
2214 | // if it is, update the index and return.
2215 | if (endEvent.touches) {
2216 | for (i = 0, l = endEvent.touches.length; i < l; i = i + 1) {
2217 | if (endEvent.touches[i].identifier === _inputIdentifier) {
2218 | _inputIndex = i;
2219 | return;
2220 | }
2221 | }
2222 | }
2223 |
2224 | // Complete the scroll. Note that touch end events
2225 | // don't capture coordinates.
2226 | _endScroll(endEvent.timeStamp, endEvent);
2227 | };
2228 |
2229 | /**
2230 | * Mouse event handlers
2231 | */
2232 | _onMouseDown = function _onMouseDown(startEvent) {
2233 |
2234 | // Don't track the right mouse buttons, or a context menu
2235 | if ((startEvent.button && startEvent.button === 2) || startEvent.ctrlKey) {
2236 | return;
2237 | }
2238 |
2239 | // Capture if possible
2240 | if (_containerNode.setCapture) {
2241 | _containerNode.setCapture();
2242 | }
2243 |
2244 | // Add move & up handlers to the *document* to allow handling outside the element
2245 | document.addEventListener('mousemove', _onMouseMove, true);
2246 | document.addEventListener('mouseup', _onMouseUp, true);
2247 |
2248 | _inputIdentifier = startEvent.button || 1;
2249 | _inputIndex = 0;
2250 | _startScroll(startEvent.clientX, startEvent.clientY, startEvent.timeStamp, startEvent);
2251 | };
2252 | _onMouseMove = function _onMouseMove(moveEvent) {
2253 | if (!_inputIdentifier) {
2254 | return;
2255 | }
2256 |
2257 | _updateScroll(moveEvent.clientX, moveEvent.clientY, moveEvent.timeStamp, moveEvent);
2258 | };
2259 | _onMouseUp = function _onMouseUp(endEvent) {
2260 | if (endEvent.button && endEvent.button !== _inputIdentifier) {
2261 | return;
2262 | }
2263 |
2264 | document.removeEventListener('mousemove', _onMouseMove, true);
2265 | document.removeEventListener('mouseup', _onMouseUp, true);
2266 |
2267 | // Release capture if possible
2268 | if (_containerNode.releaseCapture) {
2269 | _containerNode.releaseCapture();
2270 | }
2271 |
2272 | _endScroll(endEvent.timeStamp, endEvent);
2273 | };
2274 |
2275 | /**
2276 | * Pointer event handlers
2277 | */
2278 | _onPointerDown = function _onPointerDown(startEvent) {
2279 |
2280 | // If there is already a pointer event being tracked, ignore subsequent.
2281 | // However, if this pointer is seen as the primary pointer, override that.
2282 | if (_inputIdentifier && !startEvent.isPrimary) {
2283 | return;
2284 | }
2285 |
2286 | // Disable specific input types if specified in the config. Separate
2287 | // out touch and other events (eg treat both pen and mouse as "mouse")
2288 | if (startEvent.pointerType === _pointerTypeTouch) {
2289 | if (_instanceOptions.disabledInputMethods.touch) {
2290 | return;
2291 | }
2292 | } else if (_instanceOptions.disabledInputMethods.mouse) {
2293 | return;
2294 | }
2295 |
2296 | _inputIdentifier = startEvent.pointerId;
2297 | _startScroll(startEvent.clientX, startEvent.clientY, startEvent.timeStamp, startEvent);
2298 | };
2299 | _onPointerMove = function _onPointerMove(moveEvent) {
2300 | if (_inputIdentifier !== moveEvent.pointerId) {
2301 | return;
2302 | }
2303 | _updateScroll(moveEvent.clientX, moveEvent.clientY, moveEvent.timeStamp, moveEvent);
2304 | };
2305 | _onPointerUp = function _onPointerUp(endEvent) {
2306 | if (_inputIdentifier !== endEvent.pointerId) {
2307 | return;
2308 | }
2309 |
2310 | _endScroll(endEvent.timeStamp, endEvent);
2311 | };
2312 | _onPointerCancel = function _onPointerCancel(endEvent) {
2313 | _endScroll(endEvent.timeStamp, endEvent);
2314 | };
2315 | _onPointerCaptureEnd = function _onPointerCaptureEnd(event) {
2316 |
2317 | // On pointer capture end - which can happen because of another element
2318 | // releasing pointer capture - don't end scrolling, but do track that
2319 | // input capture has been lost. This will result in pointers leaving
2320 | // the window possibly being lost, but further interactions will fix
2321 | // the tracking again.
2322 | _inputCaptured = false;
2323 | };
2324 |
2325 |
2326 | /**
2327 | * Prevents click actions if appropriate
2328 | */
2329 | _onClick = function _onClick(clickEvent) {
2330 |
2331 | // If a scroll action hasn't resulted in the next scroll being prevented, and a scroll
2332 | // isn't currently in progress with a different identifier, allow the click
2333 | if (!_preventClick) {
2334 | return true;
2335 | }
2336 |
2337 | // Prevent clicks using the preventDefault() and stopPropagation() handlers on the event;
2338 | // this is safe even in IE10 as this is always a "true" event, never a window.event.
2339 | clickEvent.preventDefault();
2340 | clickEvent.stopPropagation();
2341 | if (!_inputIdentifier) {
2342 | _preventClick = false;
2343 | }
2344 | return false;
2345 | };
2346 |
2347 |
2348 | /**
2349 | * Process scroll wheel/input actions as scroller scrolls
2350 | */
2351 | _onMouseScroll = function _onMouseScroll(event) {
2352 | var scrollDeltaX, scrollDeltaY;
2353 | if (_inputIdentifier !== 'scrollwheel') {
2354 | if (_inputIdentifier !== false) {
2355 | return true;
2356 | }
2357 | _inputIdentifier = 'scrollwheel';
2358 | _cumulativeScroll.x = 0;
2359 | _cumulativeScroll.y = 0;
2360 |
2361 | // Start a scroll event
2362 | if (!_startScroll(event.clientX, event.clientY, Date.now(), event)) {
2363 | return;
2364 | }
2365 | }
2366 |
2367 | // Convert the scrollwheel values to a scroll value
2368 | if (event.wheelDelta) {
2369 | if (event.wheelDeltaX) {
2370 | scrollDeltaX = event.wheelDeltaX / 2;
2371 | scrollDeltaY = event.wheelDeltaY / 2;
2372 | } else {
2373 | scrollDeltaX = 0;
2374 | scrollDeltaY = event.wheelDelta / 2;
2375 | }
2376 | } else {
2377 | if (event.axis && event.axis === event.HORIZONTAL_AXIS) {
2378 | scrollDeltaX = event.detail * -10;
2379 | scrollDeltaY = 0;
2380 | } else {
2381 | scrollDeltaX = 0;
2382 | scrollDeltaY = event.detail * -10;
2383 | }
2384 | }
2385 |
2386 | // if only one axis scroll is used, bubble up the other scroll wheel event
2387 | // e.g. if only x axis scroll is used then scrolling y axis can bubble up to scroll the page
2388 | if(!_instanceOptions.invertScrollWheel && (
2389 | (_instanceOptions.scrollingX && !_instanceOptions.scrollingY && Math.abs(scrollDeltaX) < Math.abs(scrollDeltaY)) ||
2390 | (!_instanceOptions.scrollingX && _instanceOptions.scrollingY && Math.abs(scrollDeltaX) > Math.abs(scrollDeltaY))
2391 | )) {
2392 | return;
2393 | }
2394 |
2395 | // If the scroller is constrained to an x axis, convert y scroll to allow single-axis scroll
2396 | // wheels to scroll constrained content.
2397 | if (_instanceOptions.invertScrollWheel && !_instanceOptions.scrollingY && !scrollDeltaX) {
2398 | scrollDeltaX = scrollDeltaY;
2399 | scrollDeltaY = 0;
2400 | }
2401 |
2402 | _cumulativeScroll.x = Math.round(_cumulativeScroll.x + scrollDeltaX);
2403 | _cumulativeScroll.y = Math.round(_cumulativeScroll.y + scrollDeltaY);
2404 |
2405 | _updateScroll(_gestureStart.x + _cumulativeScroll.x, _gestureStart.y + _cumulativeScroll.y, event.timeStamp, event);
2406 |
2407 | // End scrolling state
2408 | if (_scrollWheelEndDebouncer) {
2409 | clearTimeout(_scrollWheelEndDebouncer);
2410 | }
2411 | _scrollWheelEndDebouncer = setTimeout(function () {
2412 | _releaseInputCapture();
2413 | _inputIdentifier = false;
2414 | _isScrolling = false;
2415 | _preventClick = false;
2416 | _isDisplayingScroll = false;
2417 | _ftscrollerMoving = false;
2418 | if (_instanceOptions.windowScrollingActiveFlag) {
2419 | window[_instanceOptions.windowScrollingActiveFlag] = false;
2420 | }
2421 | _cancelAnimation();
2422 | if (!_snapScroll()) {
2423 | _finalizeScroll();
2424 | }
2425 | }, 300);
2426 | };
2427 |
2428 | /**
2429 | * Capture and release input support, particularly allowing tracking
2430 | * of Metro pointers outside the docked view. Note that _releaseInputCapture
2431 | * should be called before the input identifier is cleared.
2432 | */
2433 | _captureInput = function _captureInput() {
2434 | if (_inputCaptured || _inputIdentifier === false || _inputIdentifier === 'scrollwheel') {
2435 | return;
2436 | }
2437 | if (_trackPointerEvents) {
2438 | _containerNode[_setPointerCapture](_inputIdentifier);
2439 | _containerNode.addEventListener(_lostPointerCapture, _onPointerCaptureEnd, false);
2440 | }
2441 | _inputCaptured = true;
2442 | };
2443 | _releaseInputCapture = function _releaseInputCapture() {
2444 | if (!_inputCaptured) {
2445 | return;
2446 | }
2447 | if (_trackPointerEvents) {
2448 | _containerNode.removeEventListener(_lostPointerCapture, _onPointerCaptureEnd, false);
2449 | _containerNode[_releasePointerCapture](_inputIdentifier);
2450 | }
2451 | _inputCaptured = false;
2452 | };
2453 |
2454 | /**
2455 | * Utility function acting as a getBoundingClientRect polyfill.
2456 | */
2457 | _getBoundingRect = function _getBoundingRect(anElement) {
2458 | if (anElement.getBoundingClientRect) {
2459 | return anElement.getBoundingClientRect();
2460 | }
2461 |
2462 | var x = 0, y = 0, eachElement = anElement;
2463 | while (eachElement) {
2464 | x = x + eachElement.offsetLeft - eachElement.scrollLeft;
2465 | y = y + eachElement.offsetTop - eachElement.scrollTop;
2466 | eachElement = eachElement.offsetParent;
2467 | }
2468 | return { left: x, top: y, width: anElement.offsetWidth, height: anElement.offsetHeight };
2469 | };
2470 |
2471 |
2472 | /* Instantiation */
2473 |
2474 | // Set up the DOM node if appropriate
2475 | _initializeDOM();
2476 |
2477 | // Update sizes
2478 | _updateDimensions();
2479 |
2480 | // Set up the event handlers
2481 | _addEventHandlers();
2482 |
2483 | // Define a public API to be returned at the bottom - this is the public-facing interface.
2484 | _publicSelf = {
2485 | destroy: destroy,
2486 | setSnapSize: setSnapSize,
2487 | scrollTo: scrollTo,
2488 | scrollBy: scrollBy,
2489 | updateDimensions: updateDimensions,
2490 | addEventListener: addEventListener,
2491 | removeEventListener: removeEventListener,
2492 | setDisabledInputMethods: setDisabledInputMethods
2493 | };
2494 |
2495 | if (Object.defineProperties) {
2496 | Object.defineProperties(_publicSelf, {
2497 | 'scrollHeight': {
2498 | get: function() { return _metrics.content.y; },
2499 | set: function(value) { throw new SyntaxError('scrollHeight is currently read-only - ignoring ' + value); }
2500 | },
2501 | 'scrollLeft': {
2502 | get: function() { return -_lastScrollPosition.x; },
2503 | set: function(value) { scrollTo(value, false, false); return -_lastScrollPosition.x; }
2504 | },
2505 | 'scrollTop': {
2506 | get: function() { return -_lastScrollPosition.y; },
2507 | set: function(value) { scrollTo(false, value, false); return -_lastScrollPosition.y; }
2508 | },
2509 | 'scrollWidth': {
2510 | get: function() { return _metrics.content.x; },
2511 | set: function(value) { throw new SyntaxError('scrollWidth is currently read-only - ignoring ' + value); }
2512 | },
2513 | 'segmentCount': {
2514 | get: function() {
2515 | if (!_instanceOptions.snapping) {
2516 | return { x: NaN, y: NaN };
2517 | }
2518 | return {
2519 | x: Math.ceil(_metrics.content.x / _snapGridSize.x),
2520 | y: Math.ceil(_metrics.content.y / _snapGridSize.y)
2521 | };
2522 | },
2523 | set: function(value) { throw new SyntaxError('segmentCount is currently read-only - ignoring ' + value); }
2524 | },
2525 | 'currentSegment': {
2526 | get: function() { return { x: _activeSegment.x, y: _activeSegment.y }; },
2527 | set: function(value) { throw new SyntaxError('currentSegment is currently read-only - ignoring ' + value); }
2528 | },
2529 | 'contentContainerNode': {
2530 | get: function() { return _contentParentNode; },
2531 | set: function(value) { throw new SyntaxError('contentContainerNode is currently read-only - ignoring ' + value); }
2532 | }
2533 | });
2534 | }
2535 |
2536 | // Return the public interface.
2537 | return _publicSelf;
2538 | };
2539 |
2540 |
2541 | /* Prototype Functions and Properties */
2542 |
2543 | /**
2544 | * The HTML to prepend to the scrollable content to wrap it. Used internally,
2545 | * and may be used to pre-wrap scrollable content. Axes can optionally
2546 | * be excluded for speed improvements.
2547 | */
2548 | FTScroller.prototype.getPrependedHTML = function (excludeXAxis, excludeYAxis, hwAccelerationClass) {
2549 | if (!hwAccelerationClass) {
2550 | if (typeof FTScrollerOptions === 'object' && FTScrollerOptions.hwAccelerationClass) {
2551 | hwAccelerationClass = FTScrollerOptions.hwAccelerationClass;
2552 | } else {
2553 | hwAccelerationClass = 'ftscroller_hwaccelerated';
2554 | }
2555 | }
2556 |
2557 | var output = '