Im working on a site right now with a scroll loop effect (when you reach the bottom of the page it seamlessly jumps back to the top creating an endless loop). Though I am having an issue trying implement an effect to rotate the individual div's based on their offsetTop.
Here is a fiddle link with the rotate effect working without the scroll loop effect-> https://jsfiddle.net/jacob_truax/bgrkewny/3/
Here is a link to a fiddle with both effects -> https://jsfiddle.net/jacob_truax/b1x4dow7/18/
As you can see in the second fiddle, adding the scroll loop effect while implementing the rotation effect breaks the code. Can someone help me figure this out please?
Here is the js for the broken fiddle
const sections = document.querySelectorAll("section")
const divTag = document.querySelector("div.Loop")
const mainTag = document.querySelector("main")
var doc = window.document,
clones = divTag.querySelectorAll('.is-clone'),
disableScroll = false,
scrollHeight = 0,
scrollPos = 0,
clonesHeight = 0,
i = 0;
const addMovement = function() {
const topViewport = divTag.offsetTop
const midViewport = topViewport + (divTag.offsetHeight / 2)
sections.forEach(section => {
const topSection = section.offsetTop
const midSection = topSection + (section.offsetHeight / 2)
const distanceToSection = (midViewport - midSection)
console.log(distanceToSection)
const image = section.querySelector(".info")
image.style.transform = `rotate(${distanceToSection}deg)`
})
}
addMovement()
function getScrollPos () {
return (divTag.offsetTop || divTag.scrollTop) - (divTag.clientTop || 0);
}
function setScrollPos (pos) {
divTag.scrollTop = pos;
}
function getClonesHeight () {
clonesHeight = 0;
for (i = 0; i < clones.length; i += 1) {
clonesHeight = clonesHeight + clones[i].offsetHeight;
}
return clonesHeight;
}
function reCalc () {
scrollPos = getScrollPos();
scrollHeight = divTag.scrollHeight;
clonesHeight = getClonesHeight();
if (scrollPos <= 0) {
setScrollPos(1); // Scroll 1 pixel to allow upwards scrolling
}
}
function scrollUpdate () {
if (!disableScroll) {
scrollPos = getScrollPos();
if (clonesHeight + scrollPos >= scrollHeight) {
// Scroll to the top when you’ve reached the bottom
setScrollPos(1); // Scroll down 1 pixel to allow upwards scrolling
disableScroll = true;
} else if (scrollPos <= 0) {
// Scroll to the bottom when you reach the top
setScrollPos(scrollHeight - clonesHeight);
disableScroll = true;
}
}
if (disableScroll) {
// Disable scroll-jumping for a short time to avoid flickering
window.setTimeout(function () {
disableScroll = false;
}, 40);
}
}
function init () {
reCalc();
divTag.addEventListener('scroll', function () {
window.requestAnimationFrame(scrollUpdate);
addMovement()
}, false);
window.addEventListener('resize', function () {
window.requestAnimationFrame(reCalc);
addMovement()
}, false);
}
if (document.readyState !== 'loading') {
init()
} else {
doc.addEventListener('DOMContentLoaded', init, false)
}
Here is the css
html,
body {
height: 100%;
/* overflow: hidden; */
}
body {
color: #000;
}
main {
height: 100%;
position: relative;
top: 0;
left: 0;
}
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
text-align: center;
z-index: 1;
}
.Loop {
position: absolute;
height: 100%;
overflow: auto;
}
section {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
}
::scrollbar {
display: none;
}
section div {
position: absolute;
z-index: 2;
text-align: center;
width: 50%;
background-color: #ff0000;
}
section img {
position: relative;
width: 50%;
background-color: #000;
}
The offsetTop property returns the top position (in pixels) relative
to the top of the offsetParent element.
Changing line #14 to use scrollTop instead works:
const topViewport = divTag.scrollTop;
Related
I'm trying to make an Infinite marquee that speeds up on scroll, https://altsdigital.com/ you can see the effect on this website, the text says "Not your usual SEO agency" and when you scroll it speeds up.
Here's what I've tried but it does not work. It does not loop properly without overlapping (keep your eye on the left side of the page, you'll notice the text briefly overlaps and then translates left to create a gap) and I am unsure on how to fix it:
Here's the code (TEXT ONLY VISIBLE ON "FULL PAGE" view):
const lerp = (current, target, factor) => {
let holder = current * (1 - factor) + target * factor;
holder = parseFloat(holder).toFixed(3);
return holder;
};
class LoopingText {
constructor(DOMElements) {
this.DOMElements = DOMElements;
this.lerpingData = {
counterOne: { current: 0, target: 0 },
counterTwo: { current: 100, target: 100 },
};
this.interpolationFactor = 0.1;
this.speed = 0.2;
this.render();
this.onScroll();
}
onScroll() {
window.addEventListener("scroll", () => {
this.lerpingData["counterOne"].target += this.speed * 5;
this.lerpingData["counterTwo"].target += this.speed * 5;
});
}
lerp() {
for (const counter in this.lerpingData) {
this.lerpingData[counter].current = lerp(
this.lerpingData[counter].current,
this.lerpingData[counter].target,
this.interpolationFactor
);
}
this.lerpingData["counterOne"].target += this.speed;
this.lerpingData["counterTwo"].target += this.speed;
if (this.lerpingData["counterOne"].target < 100) {
this.DOMElements[0].style.transform = `translate(${this.lerpingData["counterOne"].current}%, 0%)`;
} else {
this.lerpingData["counterOne"].current = -100;
this.lerpingData["counterOne"].target = -100;
}
if (this.lerpingData["counterTwo"].target < 100) {
this.DOMElements[1].style.transform = `translate(${this.lerpingData["counterTwo"].current}%, 0%)`;
} else {
this.lerpingData["counterTwo"].current = -100;
this.lerpingData["counterTwo"].target = -100;
}
}
render() {
this.lerp();
window.requestAnimationFrame(() => this.render());
}
}
let textArray = document.getElementsByClassName("item");
new LoopingText(textArray);
#import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght#0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Poppins";
}
.hero-section {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
position: relative;
width: 100%;
}
.loop-container {
position: relative;
width: 100%;
display: flex;
/* padding-right: 24px; */
}
.item {
position: absolute;
font-size: 15rem;
white-space: nowrap;
margin: 0;
}
span {
transition: all 0.2s;
cursor: default;
}
.hover:hover {
color: gray;
transition: all 0.2s;
}
<body>
<section class="hero-section">
<div class="loop-container">
<div class="item">Infinite Horizontal Looping Text</div>
<div class="item">Infinite Horizontal Looping Text</div>
</div>
</section>
<section class="hero-section">
</section>
</body>
Your items are overlapping because you're not allowing any lerping diffing when the items should switch positions.
The current value should never equal the target value. If the values match, than the current value needs to catch up the target — giving that erratic movement and wrong calculations, additionally aggravated for the two sibling elements which should be perfectly in sync to give that immediate snap-back, perceived as a fluid continuous motion.
Solution
Instead of animating two (or more) children independently,animate only the parent .loop-container.
The container should be as wide as one child element exactly.
"Push" one child element to the far left using position: absolute; left: -100%
To allow the target value to be always greater than the current value:when the target value is greater than 100 — set current to the negative difference of the two values, and target to 0
Demo time:
const lerp = (current, target, factor) => current * (1 - factor) + target * factor;
class LoopingText {
constructor(el) {
this.el = el;
this.lerp = {current: 0, target: 0};
this.interpolationFactor = 0.1;
this.speed = 0.2;
this.direction = -1; // -1 (to-left), 1 (to-right)
// Init
this.el.style.cssText = `position: relative; display: inline-flex; white-space: nowrap;`;
this.el.children[1].style.cssText = `position: absolute; left: ${100 * -this.direction}%;`;
this.events();
this.render();
}
events() {
window.addEventListener("scroll", () => this.lerp.target += this.speed * 5);
}
animate() {
this.lerp.target += this.speed;
this.lerp.current = lerp(this.lerp.current, this.lerp.target, this.interpolationFactor);
if (this.lerp.target > 100) {
this.lerp.current -= this.lerp.target;
this.lerp.target = 0;
}
const x = this.lerp.current * this.direction;
this.el.style.transform = `translateX(${x}%)`;
}
render() {
this.animate();
window.requestAnimationFrame(() => this.render());
}
}
document.querySelectorAll(".loop-container").forEach(el => new LoopingText(el));
/* QuickReset */ * { margin: 0; box-sizing: border-box; }
body { min-height: 400vh; /* force some scrollbars */ }
.hero-section {
position: relative;
top: 50vh;
overflow: hidden;
font: 900 9vw/1 sans-serif;
min-height: 100vh;
}
<section class="hero-section">
<div class="loop-container">
<div class="item">Infinite Horizontal Looping Text </div>
<div class="item">Infinite Horizontal Looping Text </div>
</div>
</section>
PS:
When animating, (unless you want an element static / immovable) you should never put an elements transformations inside an if/else logic. The element should always receive the updated transformations. Put inside the conditional logic only the values that you actually want to modify (as I did in the example above).
Please tell me, if you start touchmove from the middle of the page and move to the right, then console.log() goes right, but if you move to the left and cross the middle of the screen, console.log() will show the opposite direction, although the swipe goes in the same direction. As I understand it, this is due to the fact that touchstart coordinates are fixed from where touchmove started. How to fix this error. (see video).
https://youtu.be/_pB49S2BR2o
var initialX = null;
var initialY = null;
$("#content, .menu").on("touchstart", function(e) {
initialX = e.touches[0].clientX;
initialY = e.touches[0].clientY;
});
$("#content, .menu").on("touchmove", function(e) {
var diffX = initialX - e.touches[0].clientX;
var diffY = initialY - e.touches[0].clientY;
if (Math.abs(diffX) > Math.abs(diffY)) {
if (diffX > 0) {
console.log("Right");
} else {
console.log("Left");
}
} else {
return;
}
var width = parseInt($("body").css("width"));
clientX = e.touches[0].clientX;
width = width - clientX;
if (width >= 0 || width <= 375) {
$(".menu").css("left", "-" + width + "px");
} else {}
})
body {
margin: 0px;
overflow: hidden;
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}
.menu {
background: blue;
width: 100%;
height: 100%;
z-index: 1;
position: absolute;
left: -100%;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
#content {
width: 100%;
height: 100vh;
color: #000;
display: flex;
align-items: center;
justify-content: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<body>
<div class="menu" style="transition-duration: 0ms;">Menu slide</div>
<div id="content">
Content slide
</div>
</body>
const text = document.querySelector('#direction');
const cursorEl = document.querySelector('#cursorDir');
let lastX = null;
let active = false;
$("#content, .menu").on("touchstart", function(evt) {
active = true;
});
$("#content, .menu").on("touchend", function(evt) {
active = false;
});
$("#content, .menu").on("touchmove", function(evt) {
if (!active) return; // Run only on mousedown
if (lastX === null) {
// Set first value
lastX = evt.clientX;
return;
}
const diff = lastX - evt.clientX;
if (diff === 0) return; // Break if there's 0 pixels difference
console.log("direction: " + (diff > 0 ? "Left" : "Right"));
lastX = evt.clientX; // Set next value to check the difference
});
Is this what you are looking for?
Note: For the purpose of running this in a browser, touchstart and touchmove are replaced by mousedown and mousemove
const text = document.querySelector('#direction');
const cursorEl = document.querySelector('#cursorDir');
let lastX = null;
let active = false;
cursorEl.addEventListener('mousedown', () => { active = true; });
document.documentElement.addEventListener('mouseup', () => { active = false; });
cursorEl.addEventListener('mousemove', (evt) => {
if (!active) return; // Run only on mousedown
if (lastX === null) {
// Set first value
lastX = evt.clientX;
return;
}
const diff = lastX - evt.clientX;
if (diff === 0) return; // Break if there's 0 pixels difference
text.innerHTML = "direction: " + (diff > 0 ? "Left" : "Right");
lastX = evt.clientX; // Set next value to check the difference
});
#cursorDir {
display: block;
width: 150px;
height: 150px;
}
#cursorDir {
background: blue;
}
Click and hold to activate
<div id="cursorDir"></div>
<div id="direction"><div>
I'm trying to make an Infinite marquee that speeds up on scroll, https://altsdigital.com/ you can see the effect on this website, the text says "Not your usual SEO agency" and when you scroll it speeds up.
Here's what I've tried but it does not work. It does not loop properly without overlapping (keep your eye on the left side of the page, you'll notice the text briefly overlaps and then translates left to create a gap) and I am unsure on how to fix it:
Here's the code (TEXT ONLY VISIBLE ON "FULL PAGE" view):
const lerp = (current, target, factor) => {
let holder = current * (1 - factor) + target * factor;
holder = parseFloat(holder).toFixed(3);
return holder;
};
class LoopingText {
constructor(DOMElements) {
this.DOMElements = DOMElements;
this.lerpingData = {
counterOne: { current: 0, target: 0 },
counterTwo: { current: 100, target: 100 },
};
this.interpolationFactor = 0.1;
this.speed = 0.2;
this.render();
this.onScroll();
}
onScroll() {
window.addEventListener("scroll", () => {
this.lerpingData["counterOne"].target += this.speed * 5;
this.lerpingData["counterTwo"].target += this.speed * 5;
});
}
lerp() {
for (const counter in this.lerpingData) {
this.lerpingData[counter].current = lerp(
this.lerpingData[counter].current,
this.lerpingData[counter].target,
this.interpolationFactor
);
}
this.lerpingData["counterOne"].target += this.speed;
this.lerpingData["counterTwo"].target += this.speed;
if (this.lerpingData["counterOne"].target < 100) {
this.DOMElements[0].style.transform = `translate(${this.lerpingData["counterOne"].current}%, 0%)`;
} else {
this.lerpingData["counterOne"].current = -100;
this.lerpingData["counterOne"].target = -100;
}
if (this.lerpingData["counterTwo"].target < 100) {
this.DOMElements[1].style.transform = `translate(${this.lerpingData["counterTwo"].current}%, 0%)`;
} else {
this.lerpingData["counterTwo"].current = -100;
this.lerpingData["counterTwo"].target = -100;
}
}
render() {
this.lerp();
window.requestAnimationFrame(() => this.render());
}
}
let textArray = document.getElementsByClassName("item");
new LoopingText(textArray);
#import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght#0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Poppins";
}
.hero-section {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
position: relative;
width: 100%;
}
.loop-container {
position: relative;
width: 100%;
display: flex;
/* padding-right: 24px; */
}
.item {
position: absolute;
font-size: 15rem;
white-space: nowrap;
margin: 0;
}
span {
transition: all 0.2s;
cursor: default;
}
.hover:hover {
color: gray;
transition: all 0.2s;
}
<body>
<section class="hero-section">
<div class="loop-container">
<div class="item">Infinite Horizontal Looping Text</div>
<div class="item">Infinite Horizontal Looping Text</div>
</div>
</section>
<section class="hero-section">
</section>
</body>
Your items are overlapping because you're not allowing any lerping diffing when the items should switch positions.
The current value should never equal the target value. If the values match, than the current value needs to catch up the target — giving that erratic movement and wrong calculations, additionally aggravated for the two sibling elements which should be perfectly in sync to give that immediate snap-back, perceived as a fluid continuous motion.
Solution
Instead of animating two (or more) children independently,animate only the parent .loop-container.
The container should be as wide as one child element exactly.
"Push" one child element to the far left using position: absolute; left: -100%
To allow the target value to be always greater than the current value:when the target value is greater than 100 — set current to the negative difference of the two values, and target to 0
Demo time:
const lerp = (current, target, factor) => current * (1 - factor) + target * factor;
class LoopingText {
constructor(el) {
this.el = el;
this.lerp = {current: 0, target: 0};
this.interpolationFactor = 0.1;
this.speed = 0.2;
this.direction = -1; // -1 (to-left), 1 (to-right)
// Init
this.el.style.cssText = `position: relative; display: inline-flex; white-space: nowrap;`;
this.el.children[1].style.cssText = `position: absolute; left: ${100 * -this.direction}%;`;
this.events();
this.render();
}
events() {
window.addEventListener("scroll", () => this.lerp.target += this.speed * 5);
}
animate() {
this.lerp.target += this.speed;
this.lerp.current = lerp(this.lerp.current, this.lerp.target, this.interpolationFactor);
if (this.lerp.target > 100) {
this.lerp.current -= this.lerp.target;
this.lerp.target = 0;
}
const x = this.lerp.current * this.direction;
this.el.style.transform = `translateX(${x}%)`;
}
render() {
this.animate();
window.requestAnimationFrame(() => this.render());
}
}
document.querySelectorAll(".loop-container").forEach(el => new LoopingText(el));
/* QuickReset */ * { margin: 0; box-sizing: border-box; }
body { min-height: 400vh; /* force some scrollbars */ }
.hero-section {
position: relative;
top: 50vh;
overflow: hidden;
font: 900 9vw/1 sans-serif;
min-height: 100vh;
}
<section class="hero-section">
<div class="loop-container">
<div class="item">Infinite Horizontal Looping Text </div>
<div class="item">Infinite Horizontal Looping Text </div>
</div>
</section>
PS:
When animating, (unless you want an element static / immovable) you should never put an elements transformations inside an if/else logic. The element should always receive the updated transformations. Put inside the conditional logic only the values that you actually want to modify (as I did in the example above).
I'm really struggling with the maths behind my multilayer parallax. If I have the parallax effect at the top of the page then it works fine, however, due it potentially being anywhere on my page I need to offset the parallax and adjust the height so you don't see the background.
However, I need to work out how much I need to adjust the size of the layers, based on the depth the layer has and where it's positioned on the page. This parallax panel could be anywhere on the page, however.
Here is my code and a working example:
/**
* #author Martyn Lee Ball
* #desc Creates multi-layer Parallax effect
* #version 1.0
* #return {Array} Returns instances of classes as Array
*/
;class Parallax {
constructor(node) {
const self = this;
// Settings and defaults
self.settings = {
container: node,
height: node.clientHeight,
name: node.dataset.parallax,
layers: [],
defaultDepth: 0.5,
offset: function(element) {
let top = 0, left = 0;
do {
top += element.offsetTop || 0;
left += element.offsetLeft || 0;
element = element.offsetParent;
} while(element);
return {
top: top,
left: left
};
}.call(self, node)
}
;
// Populate layers setting with objects
(function() {
let layers = (self.settings.container).querySelectorAll('[data-layer]');
let count = 0;
for (const layer in layers) {
if (layers.hasOwnProperty(layer)) {
self.settings.layers.push({
// Get the depth, set to default depth if not set
depth: (function() {
if (layers[layer].dataset.depth !== undefined)
return parseFloat(layers[layer].dataset.depth);
return self.settings.defaultDepth;
}.call(self)),
element: layers[layer],
name: layers[layer].dataset.layer,
});
let height = function(layer) {
const self = this;
let height = self.settings.height;
return (height + (height - (height * layer.depth)));
}.call(self, self.settings.layers[count]);
layers[layer].style.height = height + "px";
}
count++;
}
}.call(this));
this.setupScrollEvent.call(this);
}
setupScrollEvent() {
const self = this;
window.addEventListener('scroll', function(e) {
self.last_known_scroll_position = window.scrollY || document.documentElement.scrollTop;
if (!self.ticking) {
window.requestAnimationFrame(function() {
self.scrolledEvent.call(self, self.last_known_scroll_position);
self.ticking = false;
});
self.ticking = true;
}
});
self.last_known_scroll_position = window.scrollY || document.documentElement.scrollTop;
self.scrolledEvent.call(self, self.last_known_scroll_position);
}
scrolledEvent(scrollY) {
const self = this;
// TODO: Check panel is in view
for (const layer in self.settings.layers) {
if ((self.settings.layers).hasOwnProperty(layer)) {
let movement = -((scrollY * self.settings.layers[layer].depth) - scrollY);
let translate3d = 'translate3d(0, ' + movement + 'px, 0)';
self.settings.layers[layer].element.style['-webkit-transform'] = translate3d;
self.settings.layers[layer].element.style['-moz-transform'] = translate3d;
self.settings.layers[layer].element.style['-ms-transform'] = translate3d;
self.settings.layers[layer].element.style['-o-transform'] = translate3d;
self.settings.layers[layer].element.style.transform = translate3d;
}
}
}
static initialize() {
this.instances = [];
// Ensure DOM has loaded
window.addEventListener('load', function() {
// Does page contain any parallax panels?
let panels = document.querySelectorAll('[data-parallax]');
if (panels.length > 0) {
// Parallax panels found, create instances of class and return them for reference
for (const panel in panels) {
if (panels.hasOwnProperty(panel)) {
this.instances.push(new this(panels[panel]));
}
}
}
}.bind(this));
return this.instances;
}
}
window.plugins = { "parallax-instances": Parallax.initialize() };
html,
body {
margin: 0;
padding: 0;
}
.layer {
height: 800px;
}
section[data-parallax] {
height: 500px;
position: relative;
overflow: hidden;
border: 1px solid black;
border-width: 1px 0 1px 0;
}
section[data-parallax] > div {
position: absolute;
z-index: -1;
background-position: bottom center;
background-size: auto;
background-repeat: no-repeat;
width: 100%;
height: 100%;
}
section[data-parallax] > div[data-layer='layer-bg'] {
background-image: url('https://i.imgur.com/F7pqpWZ.jpg');
}
section[data-parallax] > div[data-layer='layer-1'] {
background-image: url('https://i.imgur.com/uxpVhe1.png');
background-position: left bottom;
}
section[data-parallax] > div[data-layer='layer-2'] {
background-image: url('https://i.imgur.com/JeGChIm.png');
}
section[data-parallax] > div[data-layer='layer-3'] {
background-image: url('https://i.imgur.com/V7l8cxD.png');
background-position: right bottom;
}
section[data-parallax] > div[data-layer='layer-4'] {
background-image: url('https://i.imgur.com/joB5tI4.png');
}
section[data-parallax] > div[data-layer='layer-overlay'] {
background-image: url('https://i.imgur.com/h1ybMNZ.png');
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Parallax Development</title>
<script src="parallax-dev.js" type="text/javascript"></script>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<section style="padding-top: 300px;"></section>
<section data-parallax="panel-1">
<div data-layer="layer-bg" data-depth="0.10"></div>
<div data-layer="layer-1" data-depth="0.20"></div>
<div data-layer="layer-2" data-depth="0.50"></div>
<div data-layer="layer-3" data-depth="0.80"></div>
<div data-layer="layer-4" data-depth="1.0"></div>
</section>
<section style="padding-bottom: 50vh;"></section>
</body>
</html>
I want to make a very simple function in my jQuery script. When the finger/cursor touches/clicks on the screen, I want the pages to slide horizontally following the movements of the finger/cursor. I know there is a lot of plugins created by so many people, but I really don't need everybody else's solutions. The image is a visual view of how my HTML looks like. it is really simple.
The jQuery sciprt is obviously not correct, but I hope it would give you an idea about the simple function I need. I don't extra classes or fade-functions or anything.
$(document).live('touchmove' or 'mousemove', function() {
$('div[class=page_*], div[class=page_^]').[follow movements horizontally, and auto align to nearest edge when let go.];
});
Also I want to be able to do the same with one big div, so probably the width-variable of the element moving should be equal to $(window).width();. Actually I think that would be the best idea. I can always put more content inside the big div and make it larger, so keep it with that. It should be more simple to do and to focus on one element only.
So, here is my solution. I've made some changes so that now you can have more than 3 pages.
Also, I've defined a variable named threshold set to the half of a page. If you want to have a threshold bigger or smaller than the hakf of the page you will have to make some more changes.
HTML CODE:
<div class="container">
<div class="wrap">
<div class="page page1"></div>
<div class="page page2"></div>
<div class="page page3"></div>
<div class="page page4"></div>
</div>
</div>
CSS CODE:
.container, .page, .wrap {
width: 300px;
height: 400px;
}
.container {
background: #efefef;
box-shadow: 0px 0px 10px black;
overflow: hidden;
position: relative;
margin: 5px auto;
}
.wrap {
width: 1200px;
position: absolute;
top: 0;
left: 0;
}
.page {
float: left;
display: block;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
}
.page1 {
background: yellow;
}
.page2 {
background: green;
}
.page3 {
background: blue;
}
.page4 {
background: red;
}
As for the CSS code keep in mind that if you want to change the page size you will also have to change the container and wrap size.
JS CODE:
var mouseDown = false, right;
var xi, xf, leftX = 0;
var nPages = $(".page").size();
var pageSize = $(".page").width();
var threshold = pageSize/2;
var currentPage = 0;
$(".container").on("mousedown", function (e) {
mouseDown = true;
xi = e.pageX;
});
$(".container").on("mouseup", function (e) {
if (mouseDown) {
mouseDown = false;
xf = e.pageX;
leftX = parseInt($(".wrap").css("left").split("px")[0]);
if ((e.pageX - xi) < -threshold || (e.pageX - xi) > threshold) {
setFocusedPage();
} else {
restore();
}
}
});
$(".container").on("mouseleave", function (e) {
if (mouseDown) {
mouseDown = false;
xf = e.pageX;
leftX = parseInt($(".wrap").css("left").split("px")[0]);
if ((e.pageX - xi) < -threshold || (e.pageX - xi) > threshold) {
setFocusedPage();
} else {
restore();
}
}
});
$(".container").on("mousemove", function (e) {
if (mouseDown) {
$(".wrap").css({
"left": (leftX + (e.pageX - xi))
});
right = ((e.pageX - xi) < 0) ? true : false;
}
});
function restore() {
$(".wrap").stop().animate({
"left": -(currentPage * pageSize)
}, 200, function () {
leftX = parseInt($(".wrap").css("left").split("px")[0]);
});
}
function setFocusedPage() {
if (leftX >= (-threshold)) { // First Page
currentPage = 0;
} else if (leftX < (-threshold) && leftX >= (-(nPages + 1) * threshold)) { // Second to N-1 Page
(right) ? currentPage++ : currentPage--;
} else if (leftX < -((nPages + 1) * threshold)) { // Third Page
currentPage = nPages - 1;
}
$(".wrap").stop().animate({
"left": -(currentPage * pageSize)
}, 200, function () {
leftX = parseInt($(".wrap").css("left").split("px")[0]);
});
}
Remember here that if you want a different threshold you will have to make some changes especially in the setFocusedPage() function.
Here is my last DEMO