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>
Related
I've been waiting to ask this question for a long time, but couldn't, because I knew I would get bad reputation. This post was very hard to post, but I REALLY need this code...
Let's say there was a draggable element with the ID of "dragme"... You have to drag and drop the element to a specific spot. I was wondering if there is a code that does this task automatically for me when I execute a function. Lets name that function "dropElement". I am trying to drag "dragme" to my mouse position with a "dragElement" function with jquery or js.
This is what I tried:
(function() {
'use strict';
var mouseX = 0;
var mouseY = 0;
var timer = 0;
//tracks mouse position
document.body.addEventListener("mousemove", function(e) {mouseX = e.clientX; mouseY = e.clientY;});
function dropElement() {
$("#dragme").trigger($.Event("mousedown", {button: 0}));
$("body").trigger($.Event("mouseup", {button: 0, clientX: mouseX, clientY: mouseY}));
timer = setTimeout(drop, 100);
}
dropElement() //executes function and drops "dragme" to mouse position
I found the code in the question a bit complex to follow, especially with a timing function.
Instead I've gone back to basics (and vanilla JS) to think about the sequence of events. The user moves the mouse, we aren't interested unless they have put the mousedown within the element we want to drag. So this snippet sets a variable isDown which is set to true when the user puts the mouse down on the element.
Then it looks for a mousemove event on the whole window and if isDown is set it moves the element.
We also look for the mouseup event on the window and unset isDown.
The reason for looking for some events on the actual element and some on the window is because things are moving - the mouse may get out of the window before it is released for example.
let isDown = false;
const dragMe = document.querySelector('.dragme');
dragMe.addEventListener('mousedown', function() {
isDown = true;
});
window.addEventListener('mouseup', function() {
isDown = false;
});
window.addEventListener('mousemove', function() {
if (isDown) {
dragMe.style.top = event.clientY + 'px';
dragMe.style.left = event.clientX + 'px';
}
});
.dragme {
width: 5em;
height: 5em;
background-color: cyan;
position: relative;
top: 0;
left: 0;
}
<div class="dragme">Drag me</div>
I hope this sample helps you
var drag = {
elem: null,
x: 0,
y: 0,
state: false
};
var delta = {
x: 0,
y: 0
};
function dropElement(e){
var cur_offset = $("#autoDrag").offset();
$("#autoDrag").animate({
left: (e.pageX),
top: (e.pageY )
});
}
$(document).mousedown(function(e) {
dropElement(e);
})
$("#dragMe").mousedown(function(e) {
drag.elem = dragMe;
drag.x = e.pageX;
drag.y = e.pageY;
drag.state = true;
})
$(document).mousemove(function(e) {
if ( drag.state) {
delta.x = e.pageX - drag.x;
delta.y = e.pageY - drag.y;
var cur_offset = $(drag.elem).offset();
$(drag.elem).offset({
left: (cur_offset.left + delta.x),
top: (cur_offset.top + delta.y)
});
drag.x = e.pageX;
drag.y = e.pageY;
}
})
$("#dragMe").mouseup(function() {
drag.state = false;
})
#dragMe {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 80px;
height: 80px;
padding:10px;
background-color: #00a1ff;
color: white;
border-radius: 50px;
}
#autoDrag {
position: absolute;
right:0;
display: flex;
justify-content: center;
align-items: center;
text-align:center;
width: 80px;
height: 80px;
padding:10px;
background-color: #ff00ff;
color: white;
border-radius: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span id="dragMe">DragMe!</span>
<span id="autoDrag">Click somewhere I will be there!</span>
I trying to create a map framework for some games and i have a problem with recalc position of marker. Look url to test, with wheel you can resize div with image but the dot red not come back to own position. Sorry but im new on this y trying to learn more about js and css. Thanks
$('.map-live').css('width', "928px");
$('.map-live').css('height', "928px");
$('.map-live').css('background-size', "100%");
$('.map-live').bind('mousewheel DOMMouseScroll', function(event) {
var divSize = $('.map-live').css('width');
console.log(divSize);
divSize = divSize.replace('px', '')
divSize = parseInt(divSize);
console.log("oldSize: " + divSize);
var delta_px = event.originalEvent.wheelDelta > 0 ? (divSize + (divSize * 0.15)) : (divSize - (divSize * 0.15));
console.log("NewSize: " + delta_px);
$(this).css('width', delta_px + "px");
$(this).css('height', delta_px + "px");
$(this).css('background-size', "100%");
UpdatePoints();
});
$(function() {
$("#map-live").draggable();
});
document.getElementById('map-live').addEventListener('click', printPosition)
function getPosition(e) {
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
return {
x,
y
}
}
function printPosition(e) {
var position = getPosition(e);
console.log('X: ' + position.x + ' Y: ' + position.y);
var divX = parseInt($('.map-live').css('width').replace('px', ''));
var divY = parseInt($('.map-live').css('height').replace('px', ''));
var vhX = (position.x / divX) * 100;
var vhY = (position.y / divY) * 100;
console.log('vhX: ' + vhX + ' vhY: ' + vhY);
}
function UpdatePoints() {
$('.point').css('top', '2.477565353101834vh');
$('.point').css('left', '2.477565353101834vh');
$('.point').css('position', 'absolute');
}
body {
margin: 0;
overflow: hidden;
}
.map-live {
position: absolute;
left: 10px;
z-index: 9;
background-image: url(https://i.ibb.co/d2y5G1y/map.jpg);
width: 222px;
height: 222px;
transition: all 0.2s linear;
}
.point {
position: absolute;
left: 2.477565353101834vh;
top: 2.477565353101834vh;
width: 10px;
height: 10px;
background-color: red;
border-radius: 50%;
}
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div class="map-live ui-widget-content" id="map-live">
<div class="point"></div>
</div>
jsfiddle.net/f84mto52
Someone can correct me, but I believe your use of position: absolute is what is making the <div class="point"></div> stay in place.
Your UpdatePoints is setting always the same position in the div. With 'vh' you are calculating and absolute position proportional to viewport, no to parent container.
So, you are zooming the background image but the position (x, y) will be always be (x, y), positions are not zoomed. You need to recalculate which is the new position.
So you need to calculate new position.
function UpdatePoints(){
var divW = parseInt($('.map-live').css('width').replace('px',''));
var divH = parseInt($('.map-live').css('height').replace('px',''));
var topPosition = (2.477565353101834 / 928) * divH;
var leftPosition = (2.477565353101834 / 928) * divW;
$('.point').css('top', topPosition+'vh');
$('.point').css('left', leftPosition+'vh');
$('.point').css('position', 'absolute');
}
Also, instead using 'vh' I recommend to calculate the px position instead. I have added the already calculated delta_px parameter to UpdatePoints function:
<style>
.point {
position: absolute;
left: 22.99180647678502px;
top: 22.99180647678502px;
width: 10px;
height: 10px;
background-color: red;
border-radius: 50%;
}
</style>
<script>
function UpdatePoints(delta_px){
var position = (delta_px/100)*2.477565353101834;
$('.point').css('top', position+'px');
$('.point').css('left', position+'px');
$('.point').css('position', 'absolute');
}
</script>
Also, here we are calculating the top-left position of the .point element, not the position for the center. As it is a circle, it work fine, but if you use any other shape the position translation should be calculated from its center.
I recommend to do some research about how to translate elements. You can start here:
Calculating relative position of points when zoomed in and enlarged by a rectangle!
Zoom in on a point (using scale and translate)!
How do I effectively calculate zoom scale?!
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>
I have a list of images , which need to scrolled on left or right arrow key press. But the scrolling needs to along a curve like a parabolic path . Is there a way to do this in javascript and css? If not not what should i use for this?
for your next question, please provide examples of what you have tried, what has failed, what you have researched or found out, etc. In general, please read "How do I ask a good question?" and "How to create a Minimal, Complete, and Verifiable example".
Nonetheless, I wanted to see what I can code in a few minutes before leaving work so here is a little something that might send you along the right way:
const circle = document.querySelector('.circle');
const path = function(x) {
return 0.1 * (x * x); // or any other functon
}
let x = 0;
const updateCircle = function() {
const y = path(x); // get y from x
const speed = 5;
const translateX = speed * x + 'px';
const translatey = speed * y + 'px';
const transform = `transform: translate(${translateX}, -${translatey})`;
circle.setAttribute('style', transform);
}
document.addEventListener('keydown', function(event) {
let move = false; // set move to true if left or right arrow pressed
switch (event.key) {
case "ArrowLeft": // moving left means decreasing x values
x--;
move = true;
break;
case "ArrowRight": // moving right means increasing x values
x++;
move = true;
break;
}
if (move) { // let or right arrow pressed
window.requestAnimationFrame(updateCircle);
}
}, true);
.circle {
background: blue;
width: 50px;
height: 50px;
border-radius: 50%;
position: absolute;
top: calc(100% - 50px);
left: calc(50% - 25px);
transition: transform 170ms linear;
}
body::before {
position: absolute;
top: 1rem;
left: 50%;
transform: translateX(-50%);
display: block;
content: 'click here to focus the snippet';
color: lightgray;
border: 1px solid lightgray;
padding: 1rem;
}
<div class="circle"></div>
I have this jsfiddle where the white square always appear at the top upper-left hand corner.
http://jsfiddle.net/helpme128/3kwwo53t/2/
I want to make the white square appear at the centre instead.
I tried to make the code change below (x = 150, y = 150);
.directive('ngDraggable', function($document) { return {
restrict: 'A',
scope: {
dragOptions: '=ngDraggable'
},
link: function(scope, elem, attr) {
var startX, startY, x = 150, y = 150,
start, stop, drag, container;
However, it did not work. What should be the right code to get the white square to appear at the centre?
If all you want to do is have the white square start out at a certain position, then give it the position in the stylesheet.
left: 130px;
top: 130px;
Updated fiddle: http://jsfiddle.net/MrLister/3kwwo53t/6/
The JavaScript is used for dragging, not for defining the initial status.
By the way, it's 130px, not 150. Since the outer div is 300px across, and the inner square measures 40px, its offset needs to be -20px from the center.
Edit: or, if you want to be independent of the size of the container, set the container's position to relative and calculate the position like this:
left: calc(50% - 20px);
top: calc(50% - 20px);
Newer fiddle: http://jsfiddle.net/MrLister/3kwwo53t/7/
Be aware though that only modern browsers support calc.
Try this, if you don't want to hard code pixels,
#container {
width : 300px;
height: 300px;
background-color: black;
display:table-cell;
vertical-align:middle;
text-align:center;
}
.shape {
width : 40px;
height: 40px;
background-color: white;
margin-left:auto;
margin-right:auto;
}
Try this link - https://jsfiddle.net/3kwwo53t/8/
Call setPosition() in the link function after everything is set up:
.directive('ngDraggable', function($document) {
return {
restrict: 'A',
scope: {
dragOptions: '=ngDraggable'
},
link: function(scope, elem, attr) {
var startX, startY, x = 150, y = 150,
start, stop, drag, container;
var width = elem[0].offsetWidth,
height = elem[0].offsetHeight;
// Obtain drag options
if (scope.dragOptions) {
start = scope.dragOptions.start;
drag = scope.dragOptions.drag;
stop = scope.dragOptions.stop;
var id = scope.dragOptions.container;
if (id) {
container = document.getElementById(id).getBoundingClientRect();
}
}
// Bind mousedown event
elem.on('mousedown', function(e) {
e.preventDefault();
startX = e.clientX - elem[0].offsetLeft;
startY = e.clientY - elem[0].offsetTop;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
if (start) start(e);
});
# The position of the element is not changed until the `setPosition` function
# is called
setPosition()
// Handle drag event
function mousemove(e) {
y = e.clientY - startY;
x = e.clientX - startX;
setPosition();
if (drag) drag(e);
}
// Unbind drag events
function mouseup(e) {
$document.unbind('mousemove', mousemove);
$document.unbind('mouseup', mouseup);
if (stop) stop(e);
}
// Move element, within container if provided
function setPosition() {
if (container) {
if (x < container.left) {
x = container.left;
} else if (x > container.right - width) {
x = container.right - width;
}
if (y < container.top) {
y = container.top;
} else if (y > container.bottom - height) {
y = container.bottom - height;
}
}
elem.css({
top: y + 'px',
left: x + 'px'
});
}
}
}
})
When using absolute positioning you can center an element using top: 50%; left: 50%; and then move back the element by the half of its width:
That way, it doesn't matter how big or small the parent container is – the .shape will always be centered.
.shape {
position: absolute;
width : 40px;
height: 40px;
top: 50%;
margin-top: -20px;
left: 50%;
margin-left: -20px;
background-color: white;
}
http://jsfiddle.net/3kwwo53t/5/