Can a scrollable DIV update a Range Slider? - javascript

I have a range slider that is scrolling the content in a DIV.
If you click on the scrollable area you will see that you can drag the the scrollable DIV to the left and right. However, when you do this the range is not updated. Is there a way to update the range when the DIV is manually scrolled? I have tried to get the onscroll() position to update the range with no luck.
https://jsfiddle.net/esoteric/fxtonew7/5/
//Range Slider
var scroll = document.getElementById("scroll-range");
var panel = document.getElementById("scrolling-container");
var total = panel.scrollWidth - panel.offsetWidth;
var percentage = total * (this.value / 100);
var percentageWidth = total / 10; //10% for each +/- click
scroll.oninput = function() {
panel = document.getElementById("scrolling-container");
total = panel.scrollWidth - panel.offsetWidth;
percentage = total * (this.value / 100);
panel.scrollLeft = percentage;
}
//Foward Arrow
document.getElementById("increase").addEventListener("click",
function() {
scroll.stepUp(10);
panel.scrollBy(percentageWidth, 0)
});
//Back Arrow
document.getElementById("decrease").addEventListener("click",
function() {
scroll.stepUp(-10);
panel.scrollBy(-percentageWidth, 0)
});
//Desktop grab and scroll
const slider = document.getElementById("scrolling-container");
let isDown = false;
let startX;
let scrollLeft;
slider.addEventListener('mousedown', (e) => {
isDown = true;
slider.classList.add('active');
startX = e.pageX - slider.offsetLeft;
scrollLeft = slider.scrollLeft;
});
slider.addEventListener('mouseleave', () => {
isDown = false;
slider.classList.remove('active');
});
slider.addEventListener('mouseup', () => {
isDown = false;
slider.classList.remove('active');
});
slider.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - slider.offsetLeft;
const walk = (x - startX) * 3;
slider.scrollLeft = scrollLeft - walk;
});

You need to calculate the opposite of what you do for when your drag the slider
panel.addEventListener('scroll', (e)=>{
total = panel.scrollWidth - panel.offsetWidth;
percentage = panel.scrollLeft / total
scroll.value = percentage * 100
})
updated fiddle: https://jsfiddle.net/gaby/dwvtLn4g/8/

What you would have to do is test wherein the "panel" where you scrolling to and update the scrollbar position
you can do that by adding another panel event listener
panel.addEventListener('mousemove', (e) => {
// get scroll position in px
console.log(panel.scrollLeft, panel.scrollTop);
});
This will print the left position in the console

Listen for scroll events on your div, then find out how much percent of the scroll was done by dividing the scroll position with the possible max scroll and then set the value (as your range goes from 0 to 100) to that percentage
slider.addEventListener('scroll', e => {
const maxScrollLeft = slider.scrollWidth - scroll.clientWidth
const percentage = slider.scrollLeft / maxScrollLeft
scroll.value = percentage * 100 // as percentage is normalized from 0 to 1
})

Related

Can't add Loop to Drag Scroll Slider without breaking the dragging functionality

I've just followed this tutorial on Youtube to add a drag scroll to any scroll elements (https://www.youtube.com/watch?v=C9EWifQ5xqA&t=115s&ab_channel=WesBos) It worked and was extremely helpful. My only issue is I would like to apply this to multiple sliders on one page. I believe I have to create some form of 'For Loop' but I'm struggling to get anything working. Whenever I seem to add a For Loop it breaks the functionality and stops working.
Heres the JS I currently have which works.
var scrollSlider = document.querySelector(".scroll-slider1, .scroll-slider2");
let isDown = false;
let startX;
let scrollLeft;
scrollSlider.addEventListener('mousedown', (e) => {
e.preventDefault();
isDown = true;
scrollSlider.classList.add('active');
startX = e.pageX;
scrollLeft = scrollSlider.scrollLeft;
});
scrollSlider.addEventListener('mouseleave', () => {
isDown = false;
scrollSlider.classList.remove('active');
});
scrollSlider.addEventListener('mouseup', () => {
isDown = false;
scrollSlider.classList.remove('active');
});
scrollSlider.addEventListener('mousemove', (e) => {
e.preventDefault();
if(!isDown) return;
const x = e.pageX;
const scroll = x - startX;
scrollSlider.scrollLeft = scrollLeft - scroll;
});
Could anyone help me add some form of loop to this, so it gets applied to every drag slider on the page rather than just the first slider? Thanks.
// Get all scroll sliders
const scrollSliders = document.querySelectorAll(".scroll-slider1, .scroll-slider2");
// Loop through each scroll slider
scrollSliders.forEach(scrollSlider => {
let isDown = false;
let startX;
let scrollLeft;
// Add event listeners for each scroll slider
scrollSlider.addEventListener('mousedown', (e) => {
e.preventDefault();
isDown = true;
scrollSlider.classList.add('active');
startX = e.pageX;
scrollLeft = scrollSlider.scrollLeft;
});
scrollSlider.addEventListener('mouseleave', () => {
isDown = false;
scrollSlider.classList.remove('active');
});
scrollSlider.addEventListener('mouseup', () => {
isDown = false;
scrollSlider.classList.remove('active');
});
scrollSlider.addEventListener('mousemove', (e) => {
e.preventDefault();
if(!isDown) return;
const x = e.pageX;
const scroll = x - startX;
scrollSlider.scrollLeft = scrollLeft - scroll;
});
});

Positioning absolute element relative to viewport

I'm making a slider from scratch and I'm having an issue with the absolute positioning which has to be relative to the viewport (whenever the window height changes). Basically the slider has a bar and a button (to slide) and I wrote code to get the relative button position to the bar:
const getRelativeCoordinates = (event, referenceElement) => {
const position = {
x: event.pageX,
y: event.pageY
};
const offset = {
left: referenceElement.offsetLeft,
top: referenceElement.offsetTop
};
return {
x: position.x - offset.left,
y: position.y - offset.top,
};
}
and I wrote code to know after a cursor slide where the button would change his position and what percentage 1-100 actually is:
const handleSlider = (e) => {
const sliderBar = document.getElementById('slider-bar')
const sliderBtn = document.getElementById('slider-button')
const sliderHeight = parseInt(sliderBar.getBoundingClientRect().height) - sliderBarOffsetY
const cursorY = getRelativeCoordinates(e, document.getElementById('slider-bar')).y
let slidingPerc = ((sliderHeight - cursorY) / sliderHeight) * 100
if (slidingPerc > 100) slidingPerc = 100
else if (slidingPerc < 0) slidingPerc = 0
let barPerc = sliderBarMaxY - ((slidingPerc * (sliderBarMaxY - sliderBarMinY)) / 100)
return {
barPerc,
slidingPerc
}
}
The problem is that sliderBarMaxY and sliderBarMinY (83 and 9) are constants of max and min values of the button absolute top position in percentage (83% and 9%). These values changing the viewport height would not work anymore and if I try to change them by window.innerHeight (83 : 460 = x : innerHeight) gives an offset-error (in my case of 17)

Drag div content by grabbing its background

When the content goes outside the div, we use scrollbars to see it. How can I scroll the div content by grabbing and dragging its background? I've searched the solution but did not find what I need. Here is my fiddle:
https://jsfiddle.net/vaxobasilidze/xhn49e1j/
Drag any item to the right div and move it outside the container to the right or bottom. scrollbars appear to help you to scroll. Here is an example of what I want to achieve. See the first diagram on the link and drag it:
https://jsplumbtoolkit.com/
Any tips on how to do this?
You should just need to detect when the mouse is down and then when the mouse is moving afterwards you can store the previous mouse coordinates and reference the current coordinates. Finally you can scroll the div in question by an amount based on the difference in drag since the last mousemove call.
var mouseDown = false;
var prevCoords = { x: 0, y: 0 };
$("#mainDiv").mousedown(function() {
mouseDown = true;
}).mousemove(function(e) {
var currentScrollX = $('#mainDiv').scrollLeft();
var currentScrollY = $('#mainDiv').scrollTop();
if(mouseDown) {
$('#mainDiv').scrollLeft(currentScrollX + prevCoords.x - (e.clientX + currentScrollX))
$('#mainDiv').scrollTop(currentScrollY + prevCoords.y - e.clientY)
};
prevCoords.x = e.clientX + currentScrollX;
prevCoords.y = e.clientY;
}).mouseup(function() {
mouseDown = false;
});
https://jsfiddle.net/6rx30muh/
EDIT: Fixed bug with wiggling tables when dragging:
var mouseDown = false;
var prevCoords = { x: 0, y: 0 };
$("#mainDiv").mousedown(function() {
mouseDown = true;
}).mousemove(function(e) {
var currentScrollX = $('#mainDiv').scrollLeft();
var currentScrollY = $('#mainDiv').scrollTop();
if(mouseDown) {
$('#mainDiv').scrollLeft(currentScrollX + prevCoords.x - e.clientX)
$('#mainDiv').scrollTop(currentScrollY + prevCoords.y - e.clientY)
};
prevCoords.x = e.clientX;
prevCoords.y = e.clientY;
}).mouseup(function() {
mouseDown = false;
});
Check for mousemove between mousedown and mouseup on the body element is a good place to start.
element = $('body');
element.addEventListener("mousedown", function(){
flag = 0;
}, false);
element.addEventListener("mousemove", function(){
flag = 1;
}, false);
element.addEventListener("mouseup", function(){
if(flag === 0){
console.log("click");
}
else if(flag === 1){
console.log("drag");
}
}, false);

convert function to use css translate3d

I've written a function in Javascript to make images draggable within a container. Even if the image is enlarged it can be dragged all over the screen without disappearing from it. My function relies heaving on using style.top and style.left. Now I've heard that using translate3d might provide better performance. This is interesting because I changed my image scale function, which uses a slider, to scale3d and the scaling is clearly smoother, no doubt. So could anyone help me convert this function I've written to use translate3d? I've tried and tried but have kept failing. Many Thanks:
EDIT: I put up a jsfiddle
https://jsfiddle.net/bx4073tr/
Please note that imgRect is the parent div while img is the image itself (it's in an img tag contained in the div).
function makeImageDraggable(event) {
// Make an image draggable but within bounds of container
let overflow_vertical = false;
let overflow_horizontal = false;
// bounding rectangles to hold image and imageContainer
let imgRect = img.getBoundingClientRect();
let imgContainerRect = imageContainer.getBoundingClientRect();
// find out if image overflows it's container div
// check for vertical overflow, getBoundingClientRect().height will get the real height after the image is scaled
if ( imgRect.height > imageContainer.offsetHeight ) {
overflow_vertical = true;
}
// check for horizontal overflow
if ( imgRect.width > imageContainer.offsetWidth ) {
overflow_horizontal = true;
}
// if there is no overflow, either horizontal or vertical, then do absolutely nothing
if (!overflow_horizontal && !overflow_vertical) {
// nothing to do
} else {
// otherwise make image draggable
event = event || window.event;
// get initial mouse position
let startX = event.clientX;
let startY = event.clientY;
// get position of image to be dragged
let offsetX = pixelToFloat(img.style.left);
let offsetY = pixelToFloat(img.style.top);
// add onmousemove event now we are sure user has initiated a mousedown event
window.onmousemove = function(mousemove_event) {
if (mousemove_event == null) {
mousemove_event = window.event;
}
// calculate bounds so that image does not go off the page
// if there is an overflow, the image will be bigger than the container
// so we need to find the maximum distance we can go upwards, downwards and sideways
// using img.getBoundingClientRect, we can get the width of the scaled image, we also get the width of the container
// divide it by 2 so we can move the same number of pixels in either direction
// max right and left
let max_right = -1 * ( ((imgRect.right - imgRect.left) - (imgContainerRect.right - imgContainerRect.left))/2 );
// should be a positive number
let max_left = -1 * (max_right);
// max bottom and top
let max_bottom = -1 * ( ((imgRect.bottom - imgRect.top) - (imgContainerRect.bottom - imgContainerRect.top))/2 );
// should be a positive number
let max_top = -1 * (max_bottom);
// Dragging image left and right
if (!overflow_horizontal) {
} else {
let scrollX = (offsetX + mousemove_event.clientX - startX);
// img.style.left will keep increasing or decreasing, check if it approaches max_left or max_right
if (scrollX >= max_left || scrollX <= max_right) {
//return false;imageContainer.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
} else {
if (scrollX < max_left) { img.style.left = min(scrollX, max_left) + 'px'; }
if (scrollX > max_right) { img.style.left = max(scrollX, max_right) + 'px'; }
}
}
// Dragging image top to bottom
if (!overflow_vertical) {
} else {
let scrollY = (offsetY + mousemove_event.clientY - startY);
// as an expanded image is pulled downwards, img.style.top keeps increasing to approach max_top
// if it reaches max top, simply do nothing, else keep increasing
// check for both conditions, approaching max_top and approaching max_bottom
if (scrollY >= max_top || scrollY <= max_bottom) {
// return false;
} else {
if (scrollY < max_top) { img.style.top = min(scrollY, max_top) + 'px'; }
if (scrollY > max_bottom) { img.style.top = max(scrollY, max_bottom) + 'px'; }
}
}
// return
return false;
}
}
// cancel mousemove event on mouseup
window.onmouseup = function(mouseup_event) {
window.onmousemove = null;
// Should not return false as it will interfere with range slider
}
// return false
return false;
}
Works now.
See makeDraggable method in the fiddle below:
https://jsfiddle.net/daibatzu/0u74faz6/6/
All you have to do is add this function to the event listener for the image like:
var img = document.getElementById('myImage');
img.addEventListener('mousedown', function(event) { makeDraggable(event); });
Code
function makeDraggable(event) {
// get bounding rectangle
let imgRect = img.getBoundingClientRect();
let parentRect = parent.getBoundingClientRect();
// check overflow
let overflow_horizontal = (imgRect.width > parent.offsetWidth ? true : false);
let overflow_vertical = (imgRect.height > parent.offsetHeight ? true : false);
// get start position
let startX = event.pageX - translateX, startY = event.pageY - translateY;
let max_left = parentRect.left - imgRect.left;
let max_top = parentRect.top - imgRect.top;
window.onmousemove = function(evt) {
// set event object
if (evt == null) { evt = window.event; }
// Say max_left is 160px, this means we can only translate from 160px to -160px to keep the image visible
// so we check if the image moves beyond abs(160), if it does, set it to 160 or -160 depending on direction, else, let it continue
translateX = (Math.abs(evt.pageX - startX) >= max_left ? (max_left * Math.sign(evt.pageX - startX)) : (evt.pageX - startX));
translateY = (Math.abs(evt.pageY - startY) >= max_top ? (max_top * Math.sign(evt.pageY - startY)) : (evt.pageY - startY));
// check if scaled image width is greater than it's container. if it isn't set translateX to zero and so on
translateX = overflow_horizontal ? translateX : 0, translateY = overflow_vertical ? translateY : 0;
// translate parent div
parent.style['-webkit-transform'] = 'translate(' + translateX + 'px, ' + translateY + 'px)';
// return
return false;
}
window.onmouseup = function(evt) {
// set mousemove event to null
window.onmousemove = null;
}
return false;
};

How to Recreate the Android Facebook app's view swiping UX?

I'm looking to make something exactly like Facebook's Android app's UX for swiping between News Feed, Friend Requests, Messages, and Notifications. You should be able to "peek" at the next view by panning to the right of left, and it should snap to the next page when released if some threshold has been passed or when swiped.
Every scroll snap solution I've seen only snaps after the scrolling stops, whereas I only ever want to scroll one page at a time.
EDIT: Here's what I have so far. It seems to work fine when emulating an Android device in Google Chrome, but doesn't work when I run it on my Galaxy S4 running 4.4.2. Looking into it a bit more, it looks like touchcancel is being fired right after the first touchmove event which seems like a bug. Is there any way to get around this?
var width = parseInt($(document.body).width());
var panThreshold = 0.15;
var currentViewPage = 0;
$('.listContent').on('touchstart', function(e) {
console.log("touchstart");
currentViewPage = Math.round(this.scrollLeft / width);
});
$('.listContent').on('touchend', function(e) {
console.log("touchend");
var delta = currentViewPage * width - this.scrollLeft;
if (Math.abs(delta) > width * panThreshold) {
if (delta < 0) {
currentViewPage++;
} else {
currentViewPage--;
}
}
$(this).animate({
scrollLeft: currentViewPage * width
}, 100);
});
In case anyone wants to do this in the future, the only way I found to actually do this was to manually control all touch events and then re-implement the normally-native vertical scrolling.
It might not be the prettiest, but here's a fiddle to what I ended up doing (edited to use mouse events instead of touch events): http://jsfiddle.net/xtwzcjhL/
$(function () {
var width = parseInt($(document.body).width());
var panThreshold = 0.15;
var currentViewPage = 0;
var start; // Screen position of touchstart event
var isHorizontalScroll = false; // Locks the scrolling as horizontal
var target; // Target of the first touch event
var isFirst; // Is the first touchmove event
var beginScrollTop; // Beginning scrollTop of ul
var atanFactor = 0.6; // atan(0.6) = ~31 degrees (or less) from horizontal to be considered a horizontal scroll
var isMove = false;
$('body').on('mousedown', '.listContent', function (e) {
isMove = true;
isFirst = true;
isHorizontalScroll = false;
target = $(this);
currentViewPage = Math.round(target.scrollLeft() / width);
beginScrollTop = target.closest('ul').scrollTop();
start = {
x: e.originalEvent.screenX,
y: e.originalEvent.screenY
}
}).on('mousemove', '.listContent', function (e) {
if (!isMove) {
return false;
}
e.preventDefault();
var delta = {
x: start.x - e.originalEvent.screenX,
y: start.y - e.originalEvent.screenY
}
// If already horizontally scrolling or the first touchmove is within the atanFactor, horizontally scroll, otherwise it's a vertical scroll of the ul
if (isHorizontalScroll || (isFirst && Math.abs(delta.x * atanFactor) > Math.abs(delta.y))) {
isHorizontalScroll = true;
target.scrollLeft(currentViewPage * width + delta.x);
} else {
target.closest('ul').scrollTop(beginScrollTop + delta.y);
}
isFirst = false;
}).on('mouseup mouseout', '.listContent', function (e) {
isMove = false;
isFirst = false;
if (isHorizontalScroll) {
var delta = currentViewPage * width - target.scrollLeft();
if (Math.abs(delta) > width * panThreshold) {
if (delta < 0) {
currentViewPage++;
} else {
currentViewPage--;
}
}
$(this).animate({
scrollLeft: currentViewPage * width
}, 100);
}
});
});

Categories

Resources