I am using JQuery Draggable function and Touch Punch to produce a list of horizontal sliders that can be scrolled by clicking and dragging. It works great in touch and click devices. The problem I am facing is that if I try to scroll up or down in touch devices, it doesn't work.
I have looked through SO and found that removing "event.preventDefault" from TouchPunch allows vertical scrolling, the problem with this fix is, that it only works on some devices, and not all.
I am wondering if anyone has any other solutions, or alternative way of producing the same horizontal sliders that work on both touch and click events.
Here is Example code (JQuery Draggable):
$(function() {
var slides = $('#list1 ul').children().length;
var slideWidth = $('#list1').width();
var min = 0;
var max = -((slides - 1) * slideWidth);
$("#list1 ul").width(slides * slideWidth).draggable({
axis: 'x',
drag: function(event, ui) {
if (ui.position.left > min) ui.position.left = min;
if (ui.position.left < max) ui.position.left = max;
}
});
$("#list2 ul").width(slides * slideWidth).draggable({
axis: 'x',
drag: function(event, ui) {
if (ui.position.left > min) ui.position.left = min;
if (ui.position.left < max) ui.position.left = max;
}
});
});
#list1 {
position: relative;
height: 16em;
width: 100%;
text-align: middle;
white-space: nowrap;
overflow-x: hidden;
overflow-y: hidden;
}
#list1 .floating-box {
margin: auto;
display: inline-block;
width: 15em;
height: 13.5em;
margin: 0.1em;
border: 0.2em solid black;
background-color: white;
}
#list2 {
position: relative;
height: 16em;
width: 100%;
text-align: middle;
white-space: nowrap;
overflow-x: hidden;
overflow-y: hidden;
}
#lis2 .floating-box {
margin: auto;
display: inline-block;
width: 15em;
height: 13.5em;
margin: 0.1em;
border: 0.2em solid black;
background-color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="list1">
<ul>
<p>One</p>
<div class="floating-box">Floating box</div>
<div class="floating-box">Floating box</div>
<div class="floating-box">Floating box</div>
</ul>
</div>
<div id="list2">
<ul>
<p>Two</p>
<div class="floating-box">Floating box</div>
<div class="floating-box">Floating box</div>
<div class="floating-box">Floating box</div>
</ul>
</div>
If I touch list1 or list2 div and try to scroll up or down, it doesn't recognize the movement. Any help or direction would be appreciated.
EDIT
Based on the idea to determine theswipe direction based on the pageY propertie of touchemove.
I think it's a good idea to avoid the annoying double tap (see first answer below) on the long run.
There is still a compromise on it, but it is much more reasonable.
I tried many things... The best result I got is when I gave up on simulating a scroll on touchmove.
So here is the way to I now use each of the three touch events:
touchstart gets the initial variables like: window scrollTop and pageY.
touchmove determines the swipe direction and gets the last pageY.
touchend does the math as to were the page should scrollTo.
For the cuteness, I've put that result value in an .animate().
I was pleasantly surprised to see that it compensates quite really smoothly the fact that the page scrolls only on touchend.
I think that very few users will notice it ;).
Since "Touch-Punch" is working by default for horizontal swipes, the "compromise" only affects the vertical scroll.
Here is the code:
And a live link to try it on a touche-enabled device.
$(function() {
var slides = $('#list1 ul').children().length;
var slideWidth = $('#list1').width();
var min = 0;
var max = -((slides - 1) * slideWidth);
$(".draggable").width(slides * slideWidth).draggable({
axis: 'x',
drag: function(event, ui) {
if (ui.position.left > min) ui.position.left = min;
if (ui.position.left < max) ui.position.left = max;
}
});
var startTouchX=0;
var startTouchY=0;
var actualPosX;
var actualPosY;
var eventCounter=0;
var directionDetermined=false;
var direction;
var thisTouchX;
var thisTouchY;
var lastTouchY;
$(document).on("touchstart", function(e) {
// Actual document position
actualPosX = $(document).scrollLeft();
actualPosY = $(document).scrollTop();
// StartTouches
startTouchX = parseInt(e.originalEvent.touches[0].pageX);
startTouchY = parseInt(e.originalEvent.touches[0].pageY);
});
$(document).on("touchmove", function(e) {
// Arbitrary considering ONLY the fourth event...
// Touchmove fires way too many times!
// We only need to determine the main direction ONCE.
// This prevents an "s" swipe from messing this code.
eventCounter++;
if(eventCounter==4 && !directionDetermined){
thisTouchX = parseInt(e.originalEvent.touches[0].pageX);
thisTouchY = parseInt(e.originalEvent.touches[0].pageY);
if ( (Math.abs(thisTouchX - startTouchX)) / Math.abs(thisTouchY - startTouchY) > 1){ //check swipe direction
// HORIZONTAL
$("#debug").html("HORIZONTAL");
directionDetermined=true;
// NO NEED here. This is re-enabled on touchend, if it has been disabled.
//$(".draggable").draggable('enable');
}
else{
// VERTICAL
$("#debug").html("VERTICAL");
directionDetermined=true;
direction="vertical";
$(".draggable").draggable('disable'); // Disable draggable.
}
}
// Getting all the Y touches...
// The "last" value will be used on touchend.
lastTouchY = parseInt(e.originalEvent.touches[0].pageY);
//$("#debug").html(lastTouchY);
});
$(document).on("touchend", function(e) {
if(direction=="vertical"){
//$("#debug").html(lastTouchY);
var thisMoveY = -parseInt(lastTouchY - startTouchY);
//$("#debug").html(thisMoveY);
var newPosY = (actualPosY + thisMoveY);
//$("#debug").html(actualPosX+ " " +newPosY);
//window.scrollTo(actualPosX, newPosY);
$("html,body").animate({ scrollTop: newPosY },400);
}
// Reset everything for a future swipe.
startTouchX=0;
startTouchY=0;
eventCounter=0;
directionDetermined=false;
direction="";
$("#debug").html("");
// Re-enable draggable.
$(".draggable").draggable('enable');
});
});
First answer, using a "double-tap" to switch direction.
First, the Touch Punch website states that it's basically a jQuery-UI hack to handle some cases actually unhandled by jQuery-UI...
And that it is possible to find cases where Touch Punch fails.
Your issue was reported to the Touch Punch developpers here.
As an answer to that (in my words here), they said that it isn't really bug or an issue...
But a usage "conflict" on two different "wished" actions that are using the same touch events.
Sometimes to scroll the page, and sometimes to drag an element.
As a solution hint, they posted this Fiddle.
It suggests to find a way to disable draggable when needed.
But in this solution, the scrollable section is within the draggable element.
Which is not your case.
And you use almost all the mobile screen space for your draggable elements, so there is not enougth space left to trigger a draggable("disable") around them.
So... I had this idea, which I hope will help.
What if you'd find an elegant way to inform your users that a "double-tap" changes the movement orientation.
Here, I suggest a quite simple "double arrow" showing the movement direction.
Maybe you'll find something better.
This sure is a little compromise user experience, to ask them to double tap...
But if your layout really needs it, maybe it's ok.
So here, I reproduced your initial issue.
And here is the fix that I suggest.
I only tryed it on a Samsung Galaxy S3, but should work on every touch device.
$(function() {
var slides = $('#list1 ul').children().length;
var slideWidth = $('#list1').width();
var min = 0;
var max = -((slides - 1) * slideWidth);
$(".draggable").width(slides * slideWidth).draggable({
axis: 'x',
drag: function(event, ui) {
if (ui.position.left > min) ui.position.left = min;
if (ui.position.left < max) ui.position.left = max;
}
});
// Flag
var draggableEnabled=true;
// Find the doubletap position (to show a nice double arrow)
var tapPosition=[];
$(document).on("touchstart",function(e){
tapPosition[0] = parseInt(e.touches[0].pageX) - $(document).scrollLeft() - ($("#arrows img").width()/2);
tapPosition[1] = parseInt(e.touches[0].pageY) - $(document).scrollTop() - ($("#arrows img").width()/2);
});
Hammer(document).on("doubletap", function() {
//alert("Double tap");
draggableEnabled = !draggableEnabled; // Toggle
if(!draggableEnabled){
$(".draggable").draggable('disable'); // Disables draggable (and touch Punch)
$("#arrows img").css({
"transform":"rotate(90deg)", // Nice vertical double arrow
"top":tapPosition[1],
left:tapPosition[0]
}).fadeIn(600, function(){
$(this).fadeOut(600);
});
}else{
$(".draggable").draggable('enable'); // Enables draggable (and touch Punch)
$("#arrows img").css({
"transform":"rotate(0deg)", // Nice horizontal double arrow
"top":tapPosition[1],
left:tapPosition[0]
}).fadeIn(600, function(){
$(this).fadeOut(600);
});
}
});
});
Notice that it uses the Hammer.js (CDN) to detect the double tap.
And some extra CSS for the double arrow.
#arrows img{
width: 60vw;
height: 60vw;
position: fixed;
top:calc( 50vh - 60vw );
left:calc( 50vh - 60vw );
z-index:1000;
display:none;
}
This is the closest I have come to it, I wonder if someone could refine this code:
$(watchlist).on("touchstart", function(e) {
touchY = e.originalEvent.touches[0].pageY;
touchX = e.originalEvent.touches[0].pageX;
});
$(watchlist).on("touchmove", function(e) {
var fTouchY = e.originalEvent.touches[0].pageY;
var fTouchX = e.originalEvent.touches[0].pageX;
if ((Math.abs(fTouchX - touchX)) / Math.abs(fTouchY - touchY) > 1){ //check swipe direction
$("#watchlist ul").draggable( 'enable'); //if swipe is horizontal
}
else{
$("#watchlist ul").draggable( 'disable'); //if swipe is horizontal
}
});
The problem with this code is, that it deactivates the draggable function only after the touch move has been finished rather than during. If anyone could modify this code so that the draggable function is deactivated during as soon as the condition is met, during touchmove, rather than after, I would give them the bounty.
Related
I am using the following code to prevent page scroll on a DIV element
<script>
$(document).ready(function(){
$('#stop-scroll').on( 'mousewheel DOMMouseScroll', function (e) {
var e0 = e.originalEvent;
var delta = e0.wheelDelta || -e0.detail;
this.scrollTop += ( delta < 0 ? 1 : -1 ) * 30;
e.preventDefault();
});
});
</script>
But I want to enable scrolling again if a counter reaches to a certain value say 4, the counter will keep increase by 1 on mouse scroll. And the counter will decrease by 1 if mouse scroll up and when the counter reaches 0 the scroll disable will be enabled again and the user can scroll the page up.
Seeing your problem I have added up a code which for now based on a particular value of a particular variable enables the scrolling.
Same way you can also use condition of your choice and implement the same logic.
$(document).ready(function() {
$('#stop-scroll').on('mousewheel DOMMouseScroll', function(e) {
var e0 = e.originalEvent;
var delta = e0.wheelDelta || -e0.detail;
this.scrollTop += (delta < 0 ? 1 : -1) * 30;
if (delta <= -240) {
return true;
} else {
e.preventDefault();
}
});
});
#stop-scroll {
height: 400px;
width: 100%;
background: #6a6a6a;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="stop-scroll"></div>
Hope so the attached code helps you understand.
If still, you feel any doubt feel free to comment.
You can use the css rule overflow to control the scrolling behaviour of HTML elements.
In the example below I am using the rule overflow-y to control the scrolling behavior of the element in the Y direction. Assigning it the value of auto allows the browser to decide on its own if it thinks the container needs to have a scrollbar.
.container {
overflow-y: auto;
}
<ul class="container" style="height: 50px; background-color: orangered;">
<li>This</li>
<li>list</li>
<li>will</li>
<li>need</li>
<li>scrolling!</li>
</ul>
<ul class="container" style="height: 100px; background-color: skyblue;">
<li>This</li>
<li>list</li>
<li>won't</li>
<li>need</li>
<li>scrolling!</li>
</ul>
I have built a product catalog app with angular 2 and now I am working on debugging the warts of IE. So, this is what happens: I have a CatalogComponent that contains a few children components that display the products in categories. I have 50-60 thumbnails in page so there is not a heavy load on page. The app works perfectly fine in other browsers, performance is ok.
In CatalogComponent I have a function that detects what is the current displayed category. It listens to scroll events.
this.listeners.catalogScroll = this._renderer.listen(catalog, 'scroll',
event => self.selectCategoryByScroll(event)
);
This particular function does not do some heavy lifting. Actually it's enough to add just a simple scroll event with no code and scroll perfomance in the catalog div goes down the toilet.
catalog.addEventListener('scroll', function () {
//console.log(1);
});
Even funnier is that if I invoke scrollToCategory() it triggers a easeInQuad animation that plays smother than manually scrolling the page. I see this as proof that my page is not too heavy to render fast.
Any ideas on how to proceed to eliminate this issue?
Edit
I just observed that this happens only when scrolling by dragging the scrollbar. Scrolling by scroll wheell works like a charm, no performance dip.
As noted in the comments, the problem seems to be that IE is firing way too many scroll events.
The general solution to such problems, as described e.g. in this Q&A thread is throttling the events to some reasonable rate, e.g. like this (using requestAnimationFrame):
var scrollEventPending = false;
function handleScrollEvent () {
scrollEventPending = false;
// handle the event here
}
function throttleScrollEvents () {
if (scrollEventPending) return;
scrollEventPending = true;
requestAnimationFrame(handleScrollEvent);
});
window.addEventListener('scroll', throttleScrollEvents);
However, a limitation of this technique is that the throttled event handler still needs to fire in order to check whether a previously triggered event is pending. In your case, it seems like the event rate may be so high that even this trivial check can be enough to cause noticeable performance issues.
One possible solution could be to use the scroll event handler only to detect when the user starts scrolling, and to uninstall it temporarily until we detect by other means (e.g. comparing the page X and Y offsets) that the scrolling has stopped:
var lastX = window.pageXOffset, lastY = window.pageYOffset;
function runWhileScrolling () {
var currX = window.pageXOffset, currY = window.pageYOffset;
if (currX == lastX && currY == lastY) {
window.addEventListener('scroll', startScrolling);
return; // the page has stopped scrolling
}
// handle scrolling here
lastX = currX; lastY = currY;
requestAnimationFrame(runWhileScrolling);
}
function startScrolling () {
window.removeEventListener('scroll', startScrolling);
runWhileScrolling();
}
window.addEventListener('scroll', startScrolling);
Here's a simple live snippet demonstrating this technique, used here to emulate CSS fixed positioning:
var box = document.getElementById('jsfixed');
var lastX = window.pageXOffset, lastY = window.pageYOffset;
function runWhileScrolling () {
var currX = window.pageXOffset, currY = window.pageYOffset;
if (currX == lastX && currY == lastY) {
window.addEventListener('scroll', startScrolling);
return; // the page has stopped scrolling
}
box.style.top = currY + 80 + 'px';
box.style.left = currX + 10 + 'px';
lastX = currX; lastY = currY;
requestAnimationFrame(runWhileScrolling);
}
function startScrolling () {
window.removeEventListener('scroll', startScrolling);
runWhileScrolling();
}
window.addEventListener('scroll', startScrolling);
#cssfixed, #jsfixed {
box-sizing: border-box;
width: 250px; height: 50px;
padding: 15px 5px;
background: white;
}
#cssfixed {
position: fixed;
top: 20px; left: 10px;
border: 2px solid green;
}
#jsfixed {
position: absolute;
top: 80px; left: 10px;
border: 2px solid red;
}
body {
width: 3000px;
height: 3000px;
background: url(https://i.stack.imgur.com/ybp2X.png);
}
<div id="cssfixed">This box is kept fixed by CSS.</div>
<div id="jsfixed">This box is kept fixed by JS.</div>
Ideally, the two boxes shouldn't move at all with respect to each other as the page is scrolled. On Chrome 57, Opera 44 and Firefox 49, this is indeed the case. On IE 11, the red box does move and flicker noticeably while scrolling, but at least the scrolling itself is smooth and the box correctly returns to its original position after scrolling.
Note that using requestAnimationFrame like above typically causes the scroll handler to be called about 60 times per second while the page is scrolling. If you don't need such frequent updates, you could replace it with e.g. setTimeout(runWhileScrolling, 200) (for 5 updates per second).
I have an h1 that is far down a page..
<h1 id="scroll-to">TRIGGER EVENT WHEN SCROLLED TO.</h1>
and I want to trigger an alert when the user scrolls to the h1, or has it in it's browser's view.
$('#scroll-to').scroll(function() {
alert('you have scrolled to the h1!');
});
how do I do this?
You can calculate the offset of the element and then compare that with the scroll value like:
$(window).scroll(function() {
var hT = $('#scroll-to').offset().top,
hH = $('#scroll-to').outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop();
if (wS > (hT+hH-wH)){
console.log('H1 on the view!');
}
});
Check this Demo Fiddle
Updated Demo Fiddle no alert -- instead FadeIn() the element
Updated code to check if the element is inside the viewport or not. Thus this works whether you are scrolling up or down adding some rules to the if statement:
if (wS > (hT+hH-wH) && (hT > wS) && (wS+wH > hT+hH)){
//Do something
}
Demo Fiddle
Combining this question with the best answer from jQuery trigger action when a user scrolls past a certain part of the page
var element_position = $('#scroll-to').offset().top;
$(window).on('scroll', function() {
var y_scroll_pos = window.pageYOffset;
var scroll_pos_test = element_position;
if(y_scroll_pos > scroll_pos_test) {
//do stuff
}
});
UPDATE
I've improved the code so that it will trigger when the element is half way up the screen rather than at the very top. It will also trigger the code if the user hits the bottom of the screen and the function hasn't fired yet.
var element_position = $('#scroll-to').offset().top;
var screen_height = $(window).height();
var activation_offset = 0.5;//determines how far up the the page the element needs to be before triggering the function
var activation_point = element_position - (screen_height * activation_offset);
var max_scroll_height = $('body').height() - screen_height - 5;//-5 for a little bit of buffer
//Does something when user scrolls to it OR
//Does it when user has reached the bottom of the page and hasn't triggered the function yet
$(window).on('scroll', function() {
var y_scroll_pos = window.pageYOffset;
var element_in_view = y_scroll_pos > activation_point;
var has_reached_bottom_of_page = max_scroll_height <= y_scroll_pos && !element_in_view;
if(element_in_view || has_reached_bottom_of_page) {
//Do something
}
});
I think your best bet would be to leverage an existing library that does that very thing:
http://imakewebthings.com/waypoints/
You can add listeners to your elements that will fire off when your element hits the top of the viewport:
$('#scroll-to').waypoint(function() {
alert('you have scrolled to the h1!');
});
For an amazing demo of it in use:
http://tympanus.net/codrops/2013/07/16/on-scroll-header-effects/
Inview library triggered event and works well with jquery 1.8 and higher!
https://github.com/protonet/jquery.inview
$('div').on('inview', function (event, visible) {
if (visible == true) {
// element is now visible in the viewport
} else {
// element has gone out of viewport
}
});
Read this https://remysharp.com/2009/01/26/element-in-view-event-plugin
Fire scroll only once after a successful scroll
Note: By successful scroll I mean when the user has scrolled to the desired
element or in other words when the desired element is in view
The accepted answer worked 90% for me so I had to tweak it a little to actually fire only once.
$(window).on('scroll',function() {
var hT = $('#comment-box-section').offset().top,
hH = $('#comment-box-section').outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop();
if (wS > ((hT+hH-wH)-500)){
console.log('comment box section arrived! eh');
// This detaches the scroll so doStuff() won't run more than once
$(window).off('scroll');
doStuff();
}
});
You could use this for all devices,
$(document).on('scroll', function() {
if( $(this).scrollTop() >= $('#target_element').position().top ){
do_something();
}
});
Intersection Observer can be the best thing IMO, without any external library it does a really good job.
const options = {
root: null,
threshold: 0.25, // 0 - 1 this work as a trigger.
rootMargin: '150px'
};
const target = document.querySelector('h1#scroll-to');
const observer = new IntersectionObserver(
entries => { // each entry checks if the element is the view or not and if yes trigger the function accordingly
entries.forEach(() => {
alert('you have scrolled to the h1!')
});
}, options);
observer.observe(target);
You can use jQuery plugin with the inview event like this :
jQuery('.your-class-here').one('inview', function (event, visible) {
if (visible == true) {
//Enjoy !
}
});
Link : https://remysharp.com/2009/01/26/element-in-view-event-plugin
This should be what you need.
Javascript:
$(window).scroll(function() {
var hT = $('#circle').offset().top,
hH = $('#circle').outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop();
console.log((hT - wH), wS);
if (wS > (hT + hH - wH)) {
$('.count').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 900,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now));
}
});
}); {
$('.count').removeClass('count').addClass('counted');
};
}
});
CSS:
#circle
{
width: 100px;
height: 100px;
background: blue;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
float:left;
margin:5px;
}
.count, .counted
{
line-height: 100px;
color:white;
margin-left:30px;
font-size:25px;
}
#talkbubble {
width: 120px;
height: 80px;
background: green;
position: relative;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
float:left;
margin:20px;
}
#talkbubble:before {
content:"";
position: absolute;
right: 100%;
top: 15px;
width: 0;
height: 0;
border-top: 13px solid transparent;
border-right: 20px solid green;
border-bottom: 13px solid transparent;
}
HTML:
<div id="talkbubble"><span class="count">145</span></div>
<div style="clear:both"></div>
<div id="talkbubble"><span class="count">145</span></div>
<div style="clear:both"></div>
<div id="circle"><span class="count">1234</span></div>
Check this bootply:
http://www.bootply.com/atin_agarwal2/cJBywxX5Qp
If you are looking for a javascript version. You can call this method on scroll event listener.
showScrollTop = () =>{
const currentScrollPosition = window.pageYOffset;
let elementID = 'service-selector'
const elementOffsetTop = document.getElementById(elementID).offsetTop
if ( currentScrollPosition > elementOffsetTop){
// place your logic here
} else {
// place your logic here
}
}
window.addEventListener('scroll', showScrollTop)
If you are doing a lot of functionality based on scroll position, Scroll magic (http://scrollmagic.io/) is built entirely for this purpose.
It makes it easy to trigger JS based on when the user reaches certain elements when scrolling. It also integrates with the GSAP animation engine (https://greensock.com/) which is great for parallax scrolling websites
Just a quick modification to DaniP's answer, for anyone dealing with elements that can sometimes extend beyond the bounds of the device's viewport.
Added just a slight conditional - In the case of elements that are bigger than the viewport, the element will be revealed once it's top half has completely filled the viewport.
function elementInView(el) {
// The vertical distance between the top of the page and the top of the element.
var elementOffset = $(el).offset().top;
// The height of the element, including padding and borders.
var elementOuterHeight = $(el).outerHeight();
// Height of the window without margins, padding, borders.
var windowHeight = $(window).height();
// The vertical distance between the top of the page and the top of the viewport.
var scrollOffset = $(this).scrollTop();
if (elementOuterHeight < windowHeight) {
// Element is smaller than viewport.
if (scrollOffset > (elementOffset + elementOuterHeight - windowHeight)) {
// Element is completely inside viewport, reveal the element!
return true;
}
} else {
// Element is larger than the viewport, handle visibility differently.
// Consider it visible as soon as it's top half has filled the viewport.
if (scrollOffset > elementOffset) {
// The top of the viewport has touched the top of the element, reveal the element!
return true;
}
}
return false;
}
I use the same code doing that all the time, so added a simple jquery plugin doing it.
480 bytes long, and fast. Only bound elements analyzed in runtime.
https://www.npmjs.com/package/jquery-on-scrolled-to
It will be
$('#scroll-to').onScrolledTo(0, function() {
alert('you have scrolled to the h1!');
});
or use 0.5 instead of 0 if need to alert when half of the h1 shown.
Quick and fast implementation,
let triggered = false;
$(window).on('scroll',function() {
if (window.scrollY > ($('#scrollTo').offset().top+$('#scrollTo').outerHeight()-window.innerHeight) & !triggered){
console.log('triggered here on scroll..');
triggered = true;
}
});
using global variable triggered = false makes it just to happen once, otherwise, every time crossing past the element, this action is triggered.
I have two columns in my HTML page.
<div id="content">
<div id="left"></div>
<div id="right"></div>
</div>
Each of them occupies half of the page
#content {
height: 100%;
}
#left, #right {
float: left;
width: 50%;
height: 100%;
overflow: auto;
}
I'd like the boundary between left and right halves to be adjustable by the user. That is, the user can move the boundary to the left or to the right as he/she browses the page. Is it possible to do that somehow?
Yes, but it requires JavaScript. To apply it, you could of course just set the width of each of the sides:
var leftPercent = 50;
function updateDivision() {
document.getElementById('left').style.width = leftPercent + '%';
document.getElementById('right').style.width = (100 - leftPercent) + '%';
}
Now you can adjust the division with, say leftPercent = 50; updateDivision(), but the user isn't going to do that. There are multiple different ways you could present this to the user. Probably the best-suited way would be a little line in the middle they could drag. For this, you could use a little CSS for the positioning:
#content {
position: relative;
}
#divider {
position: absolute;
/* left to be set by JavaScript */
width: 1px;
top: 0;
bottom: 0;
background: black;
cursor: col-resize;
/* feel free to customize this, of course */
}
And then make sure you've got a div with an id of divider in content and update updateDivision to also update the left of divider:
document.getElementById('left').style.left = leftPercent + '%';
Then you just need a little logic to handle the dragging. (Here, I've put all of the elements into appropriately-named variables):
divider.addEventListener('mousedown', function(e) {
e.preventDefault();
var lastX = e.pageX;
document.documentElement.addEventListener('mousemove', moveHandler, true);
document.documentElement.addEventListener('mouseup', upHandler, true);
function moveHandler(e) {
e.preventDefault();
e.stopPropagation();
var deltaX = e.pageX - lastX;
lastX = e.pageX;
leftPercent += deltaX / parseFloat(document.defaultView.getComputedStyle(content).width) * 100;
updateDivision();
}
function upHandler(e) {
e.preventDefault();
e.stopPropagation();
document.documentElement.removeEventListener('mousemove', moveHandler, true);
document.documentElement.removeEventListener('mouseup', upHandler, true);
}
}, false);
You should be able to read it to see how it works, but in short: It listens for when someone presses on the divider. When they do, it'll attach listeners to the page for when they move their mouse. When they do, it updates the variable and calls updateDivision to update the styles. When eventually it gets a mouseup, it stops listening on the page.
As a further improvement, you could make every element have an appropriate cursor style while dragging so your cursor doesn't flash while dragging it.
Try it out.
There's nothing in the divisions so nothing will happen. It's like writing:
<h1></h1>
And changing the CSS for h1 and expecting something to be there
Usually I prefer to write my own solutions for trivial problems because generally plugins add a lot of unneeded functionality and increase your project in size. Size makes a page slower and a 30k difference (compared to jquery draggable) in a 100k pageviews / day website makes a big difference in the bill. I already use jquery and I think that's all I need for now, so please, don't tell me to use another plugin or framework to drag things around.
Whit that in mind I wrote the following code, to allow a box to be draggable around. The code works just fine (any tip about the code itself will be great appreciate), but I got a small little glitch.
When I drag the box to the browser right edge limit, a horizontal scroll bar appears, the window width gets bigger because of the box. The desirable behavior is to see no horizontal scroll bar, but allow to put part of the box outside the window area, like a windows window do.
Any tips?
CSS:
.draggable {
position: absolute;
cursor: move;
border: 1px solid black;
}
Javascript:
$(document).ready(function() {
var x = 0;
var y = 0;
$("#d").live("mousedown", function() {
var element = $(this);
$(document).mousemove(function(e) {
var x_movement = 0;
var y_movement = 0;
if (x == e.pageX || x == 0) {
x = e.pageX;
} else {
x_movement = e.pageX - x;
x = e.pageX;
}
if (y == e.pageY || y == 0) {
y = e.pageY;
} else {
y_movement = e.pageY - y;
y = e.pageY;
}
var left = parseFloat(element.css("left")) + x_movement;
element.css("left", left);
var top = parseFloat(element.css("top")) + y_movement;
element.css("top", top);
return false;
});
});
$(document).mouseup(function() {
x = 0;
y = 0;
$(document).unbind("mousemove");
});
});
HTML:
<div id="d" style="width: 100px; left: 0px; height: 100px; top: 0px;" class="draggable">a</div>
For a simple solution, you could just add some CSS to the draggable object's container to prevent the scrollbars.
body { overflow: hidden; }
jsFiddle: http://jsfiddle.net/F894P/
Instead of this :
$("#d").live("mousedown", function () {
// your code here
}); // live
try this :
$("body").on("mousedown","#d", function(){
// your code here
$("#parent_container").css({"overflow-x":"hidden"});
// or $("body").css({"overflow-x":"hidden"});
}); // on
Where #parent_container is where your draggable object is.
You should be using jQuery 1.7+
As of jQuery 1.7, the .live() method is deprecated. Use .on() to attach event handlers. Users of older versions of jQuery should use .delegate() in preference to .live().