My implementation,
http://kodhus.com/kodnest/land/PpNFTgp
I am curious, as I am not able for some reason to figure this out, how to get my JavaScript to make my slider behave more natural and smoother, if someone knows, how to, or can make this, feel free. I'd be happy to understand.
JavaScript:
const thumb = document.querySelector('.thumb');
const thumbIndicator = document.querySelector('.thumb .thumb-indicator');
const sliderContainer = document.querySelector('.slider-container');
const trackProgress = document.querySelector('.track-progress');
const sliderContainerStart = sliderContainer.offsetLeft;
const sliderContainerWidth = sliderContainer.offsetWidth;
var translate;
var dragging = false;
var percentage = 14;
document.addEventListener('mousedown', function(e) {
if (e.target.classList.contains('thumb-indicator')) {
dragging = true;
thumbIndicator.classList.add('focus');
}
});
document.addEventListener('mousemove', function(e) {
if (dragging) {
console.log('moving', e)
if (e.clientX < sliderContainerStart) {
translate = 0;
} else if (e.clientX > sliderContainerWidth + sliderContainerStart) {
translate = sliderContainerWidth;
} else {
translate = e.clientX - sliderContainer.offsetLeft;
}
thumb.style.transform = 'translate(-50%) translate(' + translate + 'px)';
trackProgress.style.transform = 'scaleX(' + translate / sliderContainerWidth + ')'
}
});
function setPercentage() {
thumb.style.transform = 'translate(-50%) translate(' + percentage/100 * sliderContainerWidth + 'px)';
trackProgress.style.transform = 'scaleX(' + percentage/100 + ')';
}
function init() {
setPercentage();
}
init();
document.addEventListener('mouseup', function(e) {
dragging = false;
thumbIndicator.classList.remove('focus');
});
EDIT: Is there a way to smoothly and naturally increment by one for every slow move?
Is it possible to make to behave as if, like when one clicks the progress bar so that it jumps there?
The kodhus site is very janky in my browser, so I can't tell if your code lacks responsiveness or whether it's the site itself. I feel that your code is a bit convoluted: translate and width / height are mixed unnecessarily; no need to use a dragging boolean when that information is always stored in the classlist. The following slider performs nicely, and has a few considerations I don't see in yours:
stopPropagation when clicking the .thumb element
drag stops if window loses focus
pointer-events: none; applied to every part of the slider but the .thumb element
let applySliderFeel = (slider, valueChangeCallback=()=>{}) => {
// Now `thumb`, `bar` and `slider` are the elements that concern us
let [ thumb, bar ] = [ '.thumb', '.bar' ].map(v => slider.querySelector(v));
let changed = amt => {
thumb.style.left = `${amt * 100}%`;
bar.style.width = `${amt * 100}%`;
valueChangeCallback(amt);
};
// Pressing down on `thumb` activates dragging
thumb.addEventListener('mousedown', evt => {
thumb.classList.add('active');
evt.preventDefault();
evt.stopPropagation();
});
// Releasing the mouse button (anywhere) deactivates dragging
document.addEventListener('mouseup', evt => thumb.classList.remove('active'));
// If the window loses focus dragging also stops - this can be a very
// nice quality of life improvement!
window.addEventListener('blur', evt => thumb.classList.remove('active'));
// Now we have to act when the mouse moves...
document.addEventListener('mousemove', evt => {
// If the drag isn't active do nothing!
if (!thumb.classList.contains('active')) return;
// Compute `xRelSlider`, which is the mouse position relative to the
// left side of the slider bar. Note that *client*X is compatible with
// getBounding*Client*Rect, and using these two values we can quickly
// get the relative x position.
let { width, left } = slider.getBoundingClientRect();
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Clamp `xRelSlider` between 0 and the slider's width
if (xRelSlider < 0) xRelSlider = 0;
if (xRelSlider > width) xRelSlider = width;
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
slider.addEventListener('mousedown', evt => {
let { width, left } = slider.getBoundingClientRect();
// Clicking the slider also activates a drag
thumb.classList.add('active');
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
changed(0);
};
let valElem = document.querySelector('.value');
applySliderFeel(document.querySelector('.slider'), amt => valElem.innerHTML = amt.toFixed(3));
.slider {
position: absolute;
width: 80%; height: 4px; background-color: rgba(0, 0, 0, 0.3);
left: 10%; top: 50%; margin-top: -2px;
}
.slider > .bar {
position: absolute;
left: 0; top: 0; width: 0; height: 100%;
background-color: #000;
pointer-events: none;
}
.slider > .thumb {
position: absolute;
width: 20px; height: 20px; background-color: #000; border-radius: 100%;
left: 0; top: 50%; margin-top: -10px;
}
.slider > .thumb.active {
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.5);
}
<div class="slider">
<div class="bar"></div>
<div class="thumb"></div>
</div>
<div class="value"></div>
Related
But When i select the other picture it did not show in magnifier box, instead it show the default picture in magnifier. how i can fix that?. I want to change the image after selecting from below and magnifier should work on that image.
and magnifier position is very downside, can we also make the appropriate position
HTML
Css
.product-image {
height: 300px;
cursor: zoom-in;
}
.magnifier-container {
display: inline-block;
position: relative;
}
.magnifier {
display: none;
position: absolute;
top: 0;
left: 100%;
overflow: hidden;
height: 300px;
width: 300px;
border: 1px solid #ddd;
border-radius: 10px;
background-color: white;
}
.magnifier__img {
width: 1000px;
transform-origin: 150px 150px;
}
js
// most efficient way to add HTML, faster than innerHTML
const parseHTML = (htmlStr) => {
const range = document.createRange();
range.selectNode(document.body); // required in Safari
return range.createContextualFragment(htmlStr);
};
// pass this function any image element to add magnifying functionality
const makeImgMagnifiable = (img) => {
const magnifierFragment = parseHTML(`
<div class="magnifier-container">
<div class="magnifier">
<img class="magnifier__img" src="${img.src}"/>
</div>
</div>
`);
// This preserves the original element reference instead of cloning it.
img.parentElement.insertBefore(magnifierFragment, img);
const magnifierContainerEl = document.querySelector(".magnifier-container");
img.remove();
magnifierContainerEl.appendChild(img);
// query the DOM for the newly added elements
const magnifierEl = magnifierContainerEl.querySelector(".magnifier");
const magnifierImg = magnifierEl.querySelector(".magnifier__img");
// set up the transform object to be mutated as mouse events occur
const transform = {
translate: [0, 0],
scale: 1,
};
// shortcut function to set the transform css property
const setTransformStyle = (el, { translate, scale }) => {
const [xPercent, yRawPercent] = translate;
const yPercent = yRawPercent < 0 ? 0 : yRawPercent;
// make manual pixel adjustments to better center
// the magnified area over the cursor.
const [xOffset, yOffset] = [
`calc(-${xPercent}% + 250px)`,
`calc(-${yPercent}% + 70px)`,
];
el.style = `
transform: scale(${scale}) translate(${xOffset}, ${yOffset});
`;
};
// show magnified thumbnail on hover
img.addEventListener("mousemove", (event) => {
const [mouseX, mouseY] = [event.pageX + 40, event.pageY - 20];
const { top, left, bottom, right } = img.getBoundingClientRect();
transform.translate = [
((mouseX - left) / right) * 100,
((mouseY - top) / bottom) * 100,
];
magnifierEl.style = `
display: block;
top: ${mouseY}px;
left: ${mouseX}px;
`;
setTransformStyle(magnifierImg, transform);
});
// zoom in/out with mouse wheel
img.addEventListener("wheel", (event) => {
event.preventDefault();
const scrollingUp = event.deltaY < 0;
const { scale } = transform;
transform.scale =
scrollingUp && scale < 3
? scale + 0.1
: !scrollingUp && scale > 1
? scale - 0.1
: scale;
setTransformStyle(magnifierImg, transform);
});
// reset after mouse leaves
img.addEventListener("mouseleave", () => {
magnifierEl.style = "";
magnifierImg.style = "";
});
};
const img = document.querySelector(".product-image");
makeImgMagnifiable(img);
Note: I've asked this question again because I was not be able to edit my old question. (No idea if this was a SO bug or a bug with my beta safari.)
So I want to generate a joystick, as it is used in many games. The joystick stands out of a background and a movable billet. The billet may only be moved within the background.
Here you can find both images
let background = new Image()
let stick = new Image()
let enableMaxDistance = false
background.onload = function() {
$(this).css({
position: "absolute",
left: "2%",
bottom: "2%",
width: "30%"
}).appendTo(document.body)
}
stick.onload = function() {
$(this).css({
position: "absolute",
left: "2%",
bottom: "2%",
width: "30%"
}).appendTo(document.body)
let zeroPosition = $(this).offset()
$(this).draggable({
drag: function(e, ui) {
let distance = Math.sqrt(Math.pow(zeroPosition.top - $(this).offset().top, 2) + Math.pow(zeroPosition.left - $(this).offset().left, 2));
if (distance > 60 && enableMaxDistance) {
e.preventDefault();
}
},
scroll: false
})
}
background.src = "https://i.stack.imgur.com/5My6q.png"
stick.src = "https://i.stack.imgur.com/YEoJ4.png"
html, body {
overflow: hidden;
height: 100%;
}
input {
margin-top: 10%;
margin-left: 50%;
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<input onclick="enableMaxDistance = !enableMaxDistance " value="toggle maximum distance" type="button"/>
But while implementing this joystick some problems occurred:
My idea was to make the stick maneuverable by using jqueryUI and to calculate its distance to the origin with each drag event. If the distance is too large, the event will be stopped (not executed) using e.preventDefault();. --> If the distance in the frame, the stick is wearable.
The problem is that ...
The stick is no longer draggable after moving out the maximum distance.
The stick should be just be movable inside the bounds without canceling the event so that I have to grab the stick again and again if I'm touching the bounds (going out of the maximum distance).
How to implement a working joystick using jQuery + jQueryUI?
The issue with your logic is that as soon as the drag event is prevented the distance value will be over 60 due to the inherent delays in JS processing time. Therefore the logic in the next drag is immediately cancelled as the distance > 60 check is immediately hit. While it would be possible to fix this, a much better solution would be to not allow the value to ever be greater than the limit you set.
To achieve this I would not recommend using jQueryUI. You can do it quite easily using native methods which give you more direct control of the positioning without having to fight against any built in logic.
It's also slightly more performant, which is vital when dealing with game mechanics; especially when dealing with direct user input which needs to be as responsive as possible.
With that said, you can use modify the basic logic as laid out in Twisty's comment on this question. Then it simply becomes a question of changing the size of the relevant elements, which is a trivial task. Try this:
var $canvas = $('#background');
var $pointer = $('#stick');
var $window = $(window);
var settings = {
width: $canvas.prop('offsetWidth'),
height: $canvas.prop('offsetHeight'),
top: $canvas.prop('offsetTop'),
left: $canvas.prop('offsetLeft')
};
settings.center = [settings.left + settings.width / 2, settings.top + settings.height / 2];
settings.radius = settings.width / 2;
let mousedown = false;
$window.on('mouseup', function() { mousedown = false; });
$pointer.on('mousedown', function() { mousedown = true; });
$pointer.on('mouseup', function() { mousedown = false; });
$pointer.on('dragstart', function(e) { e.preventDefault(); });
$window.on('mousemove', function(e) {
if (!mousedown)
return;
var result = limit(e.clientX, e.clientY);
$pointer.css('left', result.x + 'px');
$pointer.css('top', result.y + 'px');
});
function limit(x, y) {
var dist = distance([x, y], settings.center);
if (dist <= settings.radius) {
return {
x: x,
y: y
};
} else {
x = x - settings.center[0];
y = y - settings.center[1];
var radians = Math.atan2(y, x)
return {
x: Math.cos(radians) * settings.radius + settings.center[0],
y: Math.sin(radians) * settings.radius + settings.center[1]
}
}
}
function distance(dot1, dot2) {
var x1 = dot1[0],
y1 = dot1[1],
x2 = dot2[0],
y2 = dot2[1];
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
html,
body {
height: 100%;
}
#background {
background-image: url('https://i.stack.imgur.com/5My6q.png');
position: absolute;
height: 200px;
width: 200px;
top: 50%;
left: 50%;
margin: -100px 0 0 -100px;
border-radius: 200px;
border: dashed #ccc 1px;
}
#stick {
background: transparent url('https://i.stack.imgur.com/YEoJ4.png') 50% 50%;
position: absolute;
width: 100px;
height: 100px;
border-radius: 100px;
margin: -50px 0 0 -50px;
top: 50%;
left: 50%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="background"></div>
<div id="stick"></div>
I have two divs serving as two panels one to the left and one to the right.
They take 70% and 30% of the area.
I have a separator between them.
When I drag the separator to the left or right, I want that to remain as the position of the separator. i.e., I should be able to dynamically resize the left and right divs by dragging.
Here is the code I have:
http://jsbin.com/witicozi/1/edit
HTML:
<!DOCTYPE html>
<html>
<head>
<body>
<div style='height: 100px'>
<div id='left'>...</div>
<div id='separator'></div>
<div id='right'>...</div>
</div>
</body>
</html>
CSS:
#left {
float: left;
width: 70%;
height: 100%;
overflow: auto;
}
#separator {
float: left;
width: 3px;
height: 100%;
background-color: gray;
cursor: col-resize;
}
#right {
height: 100%;
overflow: auto;
}
JavaScript:
document.querySelector('#separator').addEventListener('drag', function (event) {
var newX = event.clientX;
var totalWidth = document.querySelector('#left').offsetWidth;
document.querySelector('#left').style.width = ((newX / totalWidth) * 100) + '%';
});
The problems:
The resizing happens, but the separator jumps around randomly. It even falls down many times. I have no idea what's happening.
The mouse cursor changes to a hand when the dragging begins. I want it to remain a col-resize.
It is very hard to drag.
No JQuery please.
If you use console.log(event), it shows that event.clientX doesn't return exactly what you are looking for. The following JavaScript worked for me in chrome.
document.getElementById('separator').addEventListener('drag', function(event) {
var left = document.getElementById('left');
var newX = event.offsetX + left.offsetWidth;
left.style.width = newX + 'px';
});
The event.offsetX value that it is returning is the location (in px) of the upper left hand corner of the left div. This will give you the same result but using percentages so that when the resize the window the columns adjust:
document.getElementById('separator').addEventListener('drag', function(event) {
var left = document.getElementById('left');
var newX = event.offsetX + left.offsetWidth;
left.style.width = (newX / window.innerWidth * 100) + '%';
});
Taking a bit of a different approach: rather than using the drag and drop functionality, I used some coupled mouse down and mouse up listeners. This has better cross-browser compatibility (at least as far as my testing goes) and it has the added benefit of being able to easily control the cursor.
var resize = function(event) {
var newX = event.clientX;
document.getElementById('left').style.width = (newX / window.innerWidth * 100) + '%';
};
document.getElementById('separator').addEventListener('mousedown', function(event) {
document.addEventListener('mousemove', resize);
document.body.style.cursor = 'col-resize';
});
document.addEventListener('mouseup', function(event) {
document.removeEventListener('mousemove', resize);
document.body.style.cursor = '';
});
I have implemented a parallax scrolling effect based on a tutorial I found. The effect works great. However, when I specify the background images, I am unable to control the y (vertical) axis. This is causing problems because I'm trying to set locations on multiple layered images.
Any thoughts on what's causing the problem?
Here is one external script:
$(document).ready(function(){
$('#nav').localScroll(800);
//.parallax(xPosition, speedFactor, outerHeight) options:
//xPosition - Horizontal position of the element
//inertia - speed to move relative to vertical scroll. Example: 0.1 is one tenth the speed of scrolling, 2 is twice the speed of scrolling
//outerHeight (true/false) - Whether or not jQuery should use it's outerHeight option to determine when a section is in the viewport
$('#mainimagewrapper').parallax("50%", 1.3);
$('#secondaryimagewrapper').parallax("50%", 0.5);
$('.image2').parallax("50%", -0.1);
$('#aboutwrapper').parallax("50%", 1.7);
$('.image4').parallax("50%", 1.5);
})
This is another external script:
(function( $ ){
var $window = $(window);
var windowHeight = $window.height();
$window.resize(function () {
windowHeight = $window.height();
});
$.fn.parallax = function(xpos, speedFactor, outerHeight) {
var $this = $(this);
var getHeight;
var firstTop;
var paddingTop = 0;
//get the starting position of each element to have parallax applied to it
$this.each(function(){
firstTop = $this.offset().top;
});
if (outerHeight) {
getHeight = function(jqo) {
return jqo.outerHeight(true);
};
} else {
getHeight = function(jqo) {
return jqo.height();
};
}
// setup defaults if arguments aren't specified
if (arguments.length < 1 || xpos === null) xpos = "50%";
if (arguments.length < 2 || speedFactor === null) speedFactor = 0.1;
if (arguments.length < 3 || outerHeight === null) outerHeight = true;
// function to be called whenever the window is scrolled or resized
function update(){
var pos = $window.scrollTop();
$this.each(function(){
var $element = $(this);
var top = $element.offset().top;
var height = getHeight($element);
// Check if totally above or totally below viewport
if (top + height < pos || top > pos + windowHeight) {
return;
}
$this.css('backgroundPosition', xpos + " " + Math.round((firstTop - pos) * speedFactor) + "px");
});
}
$window.bind('scroll', update).resize(update);
update();
};
})(jQuery);
Here is the CSS for one section:
#aboutwrapper {
background-image: url(../images/polaroid.png);
background-position: 50% 0;
background-repeat: no-repeat;
background-attachment: fixed;
color: white;
height: 500px;
width: 100%;
margin: 0 auto;
padding: 0;
}
#aboutwrapper .image4 {
background: url(../images/polaroid2.png) 50% 0 no-repeat fixed;
height: 500px;
width: 100%;
margin: 0 auto;
padding: 0;
}
.image3{
margin: 0 auto;
min-width: 970px;
overflow: auto;
width: 970px;
}
Both of these are being called to achieve the parallax scrolling. I really just want to more specifically control the background image locations. I've tried messing with the CSS background position and I've messed with the first javascript snippet as well. No luck.
just a quick shot, have you tried actually placing the images, either in a div or just using the img src tag to actually move the element rather than manipulating the y axis of a background image?
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