I'm trying to create a zoomed visual box with the possibility to move the visual on the Y avis from top to bottom with the mouse following.
My problem is that I need to limit the navigation to the top and the bottom of the visual to avoid white spaces over and below the image while navigate with the mouse to the extremes.
Here is my code :
HTML
<div class="follower-container">
<img src="https://picsum.photos/400/600?image=1083" class="follower-image" alt="" />
</div>
CSS
.follower-container {
position: relative;
height: 100vh;
max-width: 600px;
overflow: hidden;
border: 1px solid blue;
}
.follower-image {
position: absolute;
top: 50%;
left: 0; right: 0;
width: 100%;
display: block;
width: 100%;
height: auto;
transform: translateY(-50%);
}
JS
var mouse = { x:0, y:0 };
var image_position = { x:0, y:0 };
// Get mouse position
function getMouse(e){
mouse.x = e.clientX || e.pageX;
mouse.y = e.clientY || e.pageY;
}
$('.follower-container').mousemove(function(e){
getMouse(e);
});
// Move visual regarding mouse position
function moveImage(){
var distY = mouse.y - image_position.y;
image_position.y += distY/5;
$('.follower-image').css({
'top': image_position.y + "px"
});
}
setInterval(moveImage, 20);
My jsfiddle : https://jsfiddle.net/balix/hc2atzun/22/
You can use a combination of Math.min() and Math.max() to restrict the position to a certain range.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min
Math.max() will return the larger of the two passed numbers:
Math.max(0, 100); // 100
Math.max(12, 5); // 12
Math.min() will return the smaller of the two passed numbers:
Math.min(0, 100); // 0
Math.min(12, 5); // 5
By combining them, you can restrict a given number to be within a range. If it is outside of the range, it will max out (or min out) at the ends of the range:
function keepInRange(n, minNum, maxNum) {
return Math.min(Math.max(minNum, n), maxNum);
}
keepInRange(-12, 0, 100); // 0
keepInRange(30, 0, 100); // 30
keepInRange(777, 0, 100); // 100
Now, you just need to figure out the math behind what number range to keep your top CSS value inside of. I highly, highly recommend that you give a go at that yourself.
If you really can't figure out the solution on your own, here it is for this case: https://jsfiddle.net/oeynjcpL/
Animation gets even better with requestAnimationFrame ;)
var mouse = { x:0, y:0 };
var image_position = { x:0, y:0 };
// Get mouse position
function getMouse(e){
mouse.x = e.clientX || e.pageX;
mouse.y = e.clientY || e.pageY;
}
$('.follower-container').mousemove(function(e){
getMouse(e);
});
// Returns number n if is inside of minNum and maxNum range
// Otherwise returns minNum or maxNum
function keepInRange(n, minNum, maxNum) {
return Math.min(Math.max(minNum, n), maxNum);
}
// Move visual regarding mouse position
function moveImage(){
var distY = mouse.y - image_position.y;
image_position.y += distY/5;
// minHeight is the containing element's height minus half the height of the image
var minHeight = $('.follower-container').height() - $('.follower-image').height() / 2;
// maxHeight is half the height of the image
var maxHeight = $('.follower-image').height() / 2;
$('.follower-image').css({
'top': keepInRange(image_position.y, minHeight, maxHeight) + "px"
});
requestAnimationFrame(moveImage);
}
requestAnimationFrame(moveImage);
Related
Im trying to create a ball that follow the cursor inside my site: www.effevisual.altervista.org using wordpress and divi theme.
I tried this code lot of times without any problem but actually it looks like the objects block the ball.
I also want if possible to change the ball to less opacity when the cursor is hover a link.
<body onload = "followMouse();">
<div class="wrap">
<div id="ball"></div>
</div></body>
.wrap {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
#ball {
width: 20px;
height: 20px;
background: #0034fc;
border-radius: 50%;
position: absolute;
left: 50%;
top: 50%;
margin: -10px 0 0 -10px;
pointer-events: none;
}
var $ = document.querySelector.bind(document);
var $on = document.addEventListener.bind(document);
var xmouse, ymouse;
$on('mousemove', function (e) {
xmouse = e.clientX || e.pageX;
ymouse = e.clientY || e.pageY;
});
var ball = $('#ball');
var x = void 0,
y = void 0,
dx = void 0,
dy = void 0,
tx = 0,
ty = 0,
key = -1;
var followMouse = function followMouse() {
key = requestAnimationFrame(followMouse);
if(!x || !y) {
x = xmouse;
y = ymouse;
} else {
dx = (xmouse - x) * 0.125;
dy = (ymouse - y) * 0.125;
if(Math.abs(dx) + Math.abs(dy) < 0.1) {
x = xmouse;
y = ymouse;
} else {
x += dx;
y += dy;
}
}
ball.style.left = x + 'px';
ball.style.top = y + 'px';
};
</script>
Any message error, just the ball doesnt follow properly.
So what I found is that you are applying a transform to the class .et_pb_code_0. transform: translateX(-89px) translateY(-81px) rotateX(0deg) rotateY(0deg) rotateZ(90deg); That alone has sent your ball to a different direction making left and right up and down and up and down now left and right.
Aside from that your wrapper and ball class are contained in many other divs which position it away from the top left of the page.
I can fix it in inspecter by dragging the wrap class to the top under the main-content1 class.
You will also have to apply position:fixed to the wrap class to keep it on the screen at all times. And a z-index for the lower parks of the page. And pointer-events:none so you can click links.
And something like this to get you started:
jQuery( "li" ).mouseenter(function() {
jQuery( "#ball" ).fadeTo( "slow" , 0, function() {
// Animation complete.
});
});
jQuery( "li" ).mouseleave(function() {
jQuery( "#ball" ).fadeTo( "slow" , 1, function() {
// Animation complete.
});
});
Trying to make div move with mousescroll in opposite direction of scroll from top right to bottom left hand corner.
Right now its going from bottom left to top right
#block {
position: absolute;
top: 400px;
left: 100px;
<script>
$(function(){
var lastScrollYPos = 0;
$(window).on('scroll', function(){
var $this = $(this),
$block = $('#block'),
// retrieves the top and left coordinates
position = $block.offset(),
// X and Y factors allows to change the diagonal movement direction and
// degree. Negative values inverts the direction.
factorX = 1,
factorY = 1,
// retrieves current vertical scrolling position and calculate the
// relative offset
scrollYPos = $this.scrollTop(),
offset = Math.abs(scrollYPos - lastScrollYPos),
// mouse wheel delta is inverted respect to the direction, so we need to
// normalize it against the direction
direction = scrollYPos > lastScrollYPos ? -1 : 1,
// calculates the new position. NOTE: if integers only are used for factors,
// then `Math.floor()` isn't required.
newTop = position.top + Math.floor(direction * offset * factorY),
newLeft = position.left - Math.floor(direction * offset * factorX);
// moves the block
$block.offset({ top: newTop, left: newLeft });
lastScrollYPos = scrollYPos;
});
});
</script>
I've flipped it around, by inverting the direction (as is commented) and then adding the last Y position instead of subtracting it (i've commented that)
$(function() {
var lastScrollYPos = 0;
$(window).on('scroll', function() {
var $this = $(this),
$block = $('#block'),
// retrieves the top and left coordinates
position = $block.offset(),
// X and Y factors allows to change the diagonal movement direction and
// degree. Negative values inverts the direction.
factorX = -1,
factorY = -1,
// retrieves current vertical scrolling position and calculate the
// relative offset
scrollYPos = $this.scrollTop(),
// ---- Flip around the offset calculation
offset = Math.abs(scrollYPos + lastScrollYPos),
// mouse wheel delta is inverted respect to the direction, so we need to
// normalize it against the direction
direction = scrollYPos > lastScrollYPos ? -1 : 1,
// calculates the new position. NOTE: if integers only are used for factors,
// then `Math.floor()` isn't required.
newTop = position.top + Math.floor(direction * offset * factorY),
newLeft = position.left - Math.floor(direction * offset * factorX);
// moves the block
$block.offset({
top: newTop,
left: newLeft
});
lastScrollYPos = scrollYPos;
});
});
.body {
height: 1500px;
width: 1000px;
}
#block {
height: 750px;
width: 500px;
background: blue;
position: absolute;
top: -700px;
left: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="body">
<div id="block"></div>
</div>
I could find similar questions involving jQuery UI lib, or only css with no handle to drag, but nothing with pure maths.
What I try to perform is to have a resizable and rotatable div. So far so easy and I could do it.
But it gets more complicate when rotated, the resize handle does calculation in opposite way: it decreases the size instead of increasing when dragging away from shape.
Apart from the calculation, I would like to be able to change the cursor of the resize handle according to the rotation to always make sense.
For that I was thinking to detect which quadrant is the resize handle in and apply a class to change cursor via css.
I don't want to reinvent the wheel, but I want to have a lightweight code and simple UI. So my requirement is jQuery but nothing else. no jQuery UI.
I could develop until achieving this but it's getting too mathematical for me now.. I am quite stuck that's why I need your help to detect when the rotation is enough to have the calculation reversed.
Eventually I am looking for UX improvement if anyone has an idea or better examples to show me!
Here is my code and a Codepen to try: http://codepen.io/anon/pen/rrAWJA
<html>
<head>
<style>
html, body {height: 100%;}
#square {
width: 100px;
height: 100px;
margin: 20% auto;
background: orange;
position: relative;
}
.handle * {
position: absolute;
width: 20px;
height: 20px;
background: turquoise;
border-radius: 20px;
}
.resize {
bottom: -10px;
right: -10px;
cursor: nwse-resize;
}
.rotate {
top: -10px;
right: -10px;
cursor: alias;
}
</style>
<script type="text/javascript" src="js/jquery.js"></script>
<script>
$(document).ready(function()
{
new resizeRotate('#square');
});
var resizeRotate = function(targetElement)
{
var self = this;
self.target = $(targetElement);
self.handles = $('<div class="handle"><div class="resize" data-position="bottom-right"></div><div class="rotate"></div></div>');
self.currentRotation = 0;
self.positions = ['bottom-right', 'bottom-left', 'top-left', 'top-right'];
self.bindEvents = function()
{
self.handles
//=============================== Resize ==============================//
.on('mousedown', '.resize', function(e)
{
// Attach mouse move event only when first clicked.
$(document).on('mousemove', function(e)
{
var topLeft = self.target.offset(),
bottomRight = {x: topLeft.left + self.target.width(), y: topLeft.top + self.target.height()},
delta = {x: e.pageX - bottomRight.x, y: e.pageY - bottomRight.y};
self.target.css({width: '+=' + delta.x, height: '+=' + delta.y});
})
.one('mouseup', function(e)
{
// When releasing handle, round up width and height values :)
self.target.css({width: parseInt(self.target.width()), height: parseInt(self.target.height())});
$(document).off('mousemove');
});
})
//============================== Rotate ===============================//
.on('mousedown', '.rotate', function(e)
{
// Attach mouse move event only when first clicked.
$(document).on('mousemove', function(e)
{
var topLeft = self.target.offset(),
center = {x: topLeft.left + self.target.width() / 2, y: topLeft.top + self.target.height() / 2},
rad = Math.atan2(e.pageX - center.x, e.pageY - center.y),
deg = (rad * (180 / Math.PI) * -1) + 135;
self.currentRotation = deg;
// console.log(rad, deg);
self.target.css({transform: 'rotate(' + (deg)+ 'deg)'});
})
.one('mouseup', function(e)
{
$(document).off('mousemove');
// console.log(self.positions[parseInt(self.currentRotation/90-45)]);
$('.handle.resize').attr('data-position', self.positions[parseInt(self.currentRotation/90-45)]);
});
});
};
self.init = function()
{
self.bindEvents();
self.target.append(self.handles.clone(true));
}();
}
</script>
</head>
<body>
<div id="all">
<div id="square"></div>
</div>
</body>
</html>
Thanks for the help!
Here is a modification of your code that achieves what you want:
$(document).ready(function() {
new resizeRotate('#square');
});
var resizeRotate = function(targetElement) {
var self = this;
self.target = $(targetElement);
self.handles = $('<div class="handle"><div class="resize" data-position="bottom-right"></div><div class="rotate"></div></div>');
self.currentRotation = 0;
self.w = parseInt(self.target.width());
self.h = parseInt(self.target.height());
self.positions = ['bottom-right', 'bottom-left', 'top-left', 'top-right'];
self.bindEvents = function() {
self.handles
//=============================== Resize ==============================//
.on('mousedown', '.resize', function(e) {
// Attach mouse move event only when first clicked.
$(document).on('mousemove', function(e) {
var topLeft = self.target.offset();
var centerX = topLeft.left + self.target.width() / 2;
var centerY = topLeft.top + self.target.height() / 2;
var mouseRelativeX = e.pageX - centerX;
var mouseRelativeY = e.pageY - centerY;
//reverse rotation
var rad = self.currentRotation * Math.PI / 180;
var s = Math.sin(rad);
var c = Math.cos(rad);
var mouseLocalX = c * mouseRelativeX + s * mouseRelativeY;
var mouseLocalY = -s * mouseRelativeX + c * mouseRelativeY;
self.w = 2 * mouseLocalX;
self.h = 2 * mouseLocalY;
self.target.css({
width: self.w,
height: self.h
});
})
.one('mouseup', function(e) {
$(document).off('mousemove');
});
})
//============================== Rotate ===============================//
.on('mousedown', '.rotate', function(e) {
// Attach mouse move event only when first clicked.
$(document).on('mousemove', function(e) {
var topLeft = self.target.offset(),
center = {
x: topLeft.left + self.target.width() / 2,
y: topLeft.top + self.target.height() / 2
},
rad = Math.atan2(e.pageX - center.x, center.y - e.pageY) - Math.atan(self.w / self.h),
deg = rad * 180 / Math.PI;
self.currentRotation = deg;
self.target.css({
transform: 'rotate(' + (deg) + 'deg)'
});
})
.one('mouseup', function(e) {
$(document).off('mousemove');
$('.handle.resize').attr('data-position', self.positions[parseInt(self.currentRotation / 90 - 45)]);
});
});
};
self.init = function() {
self.bindEvents();
self.target.append(self.handles.clone(true));
}();
}
The major changes are the following:
In the resize event, the mouse position is transformed to the local coordinate system based on the current rotation. The size is then determined by the position of the mouse in the local system.
The rotate event accounts for the aspect ratio of the box (the - Math.atan(self.w / self.h) part).
If you want to change the cursor based on the current rotation, check the angle of the handle (i.e. self.currentRotation + Math.atan(self.w / self.h) * 180 / Math.PI). E.g. if you have a cursor per quadrant, just check if this value is between 0..90, 90..180 and so on. You may want to check the documentation if and when negative numbers are returned by atan2.
Note: the occasional flickering is caused by the box not being vertically centered.
Nico's answer is mostly correct, but the final result calculation is incorrect -> which is causing the flickering BTW. What is happening is the increase or decrease in size is being doubled by the 2x multiplication. The original half-width should be added to the new half-width to calculate the correct new width and height.
Instead of this:
self.w = 2 * mouseLocalX;
self.h = 2 * mouseLocalY;
It should be this:
self.w = (self.target.width() / 2) + mouseLocalX;
self.h = (self.target.height() / 2) + mouseLocalY;
I'm wanting the plane and rocket to only move approx 5% from their original place when the mouse hits the hero-panel area.
this current code makes both images follow and offset where the mouse position is.
Please assist.
$(document).ready(function () {
$('#hero-panel').mousemove(function (e) {
parallax(e, document.getElementById('plane'), 1);
parallax(e, document.getElementById('rocket'), 2);
});
});
function parallax(e, target, layer) {
var layer_coeff = 10 / layer;
var x = ($(window).width() - target.offsetWidth) / 4 - (e.pageX - ($(window).width() / 4)) / layer_coeff;
var y = ($(window).height() - target.offsetHeight) / 4 - (e.pageY - ($(window).height() / 4)) / layer_coeff;
$(target).offset({ top: y ,left : x });
};
https://jsfiddle.net/jc0807/c5yke2on/
Thanks
Ok, I think I understand what you're looking for:
fiddle
$(document).ready(function () {
var plane = document.getElementById('plane');
var rocket = document.getElementById('rocket');
plane.homePos = { x: plane.offsetLeft, y: plane.offsetTop };
rocket.homePos = { x: rocket.offsetLeft, y: rocket.offsetTop };
$('#hero-panel').mousemove(function (e) {
parallax(e, document.getElementById('plane'), 10);
parallax(e, document.getElementById('rocket'), 20);
});
});
function parallax(e, target, layer) {
var x = target.homePos.x - (e.pageX - target.homePos.x) / layer;
var y = target.homePos.y - (e.pageY - target.homePos.y) / layer;
$(target).offset({ top: y ,left : x });
};
What we're doing here is recording the starting position of the plane and the rocket as a new property 'homePos' on the plane and rocket objects. This makes it easy to apply the parallax effect as an offset from the original positions based on the mouse distance from the object homePos.
If you modify the layer value passed to parallax, the amount of movement will change (we're dividing the mouse offset from the middle of the object's starting position by it, to calculate the new object offset amount).
I guess my question is somehow related to the one above and I didn't want to create a duplicate.
I am using the code bellow to "navigate" into an image on mousemove. The problem is that I cannot manage to make the image fill all the viewable screen area. I've added a red background to the container to show what I mean. The desired result would be to no red background visible.
HTML
<div class="m-scene" id="main">
<div class="scene_element">
<img class="body" src="http://www.planwallpaper.com/static/images/nature-background-images2.jpg" />
</div>
</div>
JS
$(document).ready(function() {
$('img.body').mousemove(function(e) {
parallax(e, this, 1);
});
});
function parallax(e, target, layer) {
var layer_coeff = 20 / layer;
var x = ($(window).width() - target.offsetWidth) / 2 - (e.pageX - ($(window).width() / 2)) / layer_coeff;
var y = ($(window).height() - target.offsetHeight) / 2 - (e.pageY - ($(window).height() / 2)) / layer_coeff;
$(target).offset({
top: y,
left: x
});
};
CSS
.m-scene {
background-color: red;
overflow: hidden;
width: 100%;
}
.scene_element {
margin-left: auto;
margin-right: auto;
overflow: hidden;
}
.scene_element img {
width: 100%;
height: auto;
margin-left: auto;
margin-right: auto;
}
My jsFiddle is: fiddle
Thank you!
I'm creating a slider that displays a panoramic image by moving it horizontally inside a container.
Features or Intention (it's under development):
By default the animation happens automatically, changing the transformX value, in each step;
the user's able to touch the element and drag it left or right;
when touchend event's triggered, the slider resumes the animation from the user's dragged x position;
The problem is that when a user touch the element and starts dragging, the input value on transformX makes the element jumpy, apparently because the anchor point is different every time the user touches the draggable element.
What I'd like to know is, how to calculate the anchor point or position, where the user is dragging from? Or what's the best practice to always calculate from the same point, to avoid having this jumps.
I've done quite a lot of research at the moment without success. You can find a live example in the following link (http://jsbin.com/quhabi/1/edit). Please turn on the touch emulator on your brower because at the moment I'm not yet supporting mouse dragging.
You can also have a look into the code below (depends on jQuery):
sass:
html {
width: 100%;
height: 100%;
body {
width: 100%;
height: 100%;
background-color: #999;
.panorama {
width: 80%;
height: 80%;
margin: 0 auto;
overflow: hidden;
img {
height: 100%;
position: relative;
transition: opacity 0.6s ease;
transform-origin:left top;
}
}
}
}
html:
<div class="panorama">
<img src="images/panorama.jpg" alt="">
</div>
Javascript:
/*globals $, window */
'use strict';
$('document').ready(function () {
var panorama = {
// properties
$panorama: $('.panorama'),
$moveElement: $('.panorama img'),
timestart: 0,
seconds: 12,
msTotal: 0,
direction: -1,
positionX: 0,
percentage: 0,
animationFrameID: false,
myRequestAnimationFrame: (function () {
return function (callback) {
return window.setTimeout(callback, 1000 / 60);
};
})(),
touchPlayTimeout: 3000,
moveTimeoutID: null,
rightBoundary: null,
// methods
step: function (timestart) {
var self = this,
timestamp,
positionX;
timestamp = Date.now();
self.progress = timestamp - timestart;
self.percentage = (self.progress * (100 / self.msTotal));
positionX = self.direction * self.percentage;
positionX = self.positionBounderies(positionX);
positionX += '%';
self.position(positionX);
if (self.progress < self.msTotal) {
timestamp += 10;
self.animationFrameID = self.myRequestAnimationFrame(function () {
self.step.call(self, timestart);
});
}
},
positionBounderies: function (positionX) {
// move the next line to init method, after image preload done!
this.rightBoundary = 100 - (100 * (this.$panorama.width() / this.$moveElement.width()));
positionX = positionX > 0 ? 0 : positionX;
positionX = (positionX < 0 && Math.abs(positionX) > this.rightBoundary) ? this.direction * this.rightBoundary : positionX;
return positionX;
},
progressByPercentage: function (percentage) {
return percentage * (this.msTotal / 100);
},
dragIt: function (touchX) {
var positionX,
percentage = (this.progress * (100 / this.msTotal));
positionX = this.direction * percentage;
positionX = positionX + (touchX / 100);
positionX = this.positionBounderies(positionX);
positionX += '%';
// update percentage
this.percentage = Math.abs(parseFloat(positionX));
this.position(positionX);
},
position: function (posX) {
this.$moveElement.css('transform', 'translateX(' + posX + ')');
},
init: function () {
var self = this;
// set initial values
this.msTotal = this.seconds * 1000;
// set listeners
this.$moveElement.on('touchstart mousedown', function (e) {
// on mousedown prevent browser default `img` drag
e.preventDefault();
clearTimeout(self.animationFrameID);
clearTimeout(self.moveTimeoutID);
});
this.$moveElement.on('touchend mouseup', function (e) {
// on mousedown prevent browser default `img` drag
e.preventDefault();
// calculate where to play from using current progress
var playFrom = null;
self.progress = self.progressByPercentage(self.percentage);
self.moveTimeoutID = setTimeout(function () {
clearTimeout(self.moveTimeoutID);
playFrom = Date.now();
playFrom = playFrom - self.progress;
self.step(playFrom);
}, self.touchPlayTimeout);
});
this.$moveElement.on('touchmove', function (e) {
console.log(e);
var touch = e.originalEvent.touches[0],
touchPosition = touch.pageX - self.$panorama.width();
self.dragIt(touchPosition);
});
this.step(Date.now());
}
};
panorama.init();
});
I found that my problem is related with how I'm calculating the drag distance. The code above clearly shows that I'm using solely the position of pageX to calculate the transformX, which is absolutely wrong. The correct way is to store the start point from where the drag is happening from and the current dragging x value should be used to calculate the distance.
For example,
this.$moveElement.on('touchstart mousedown', function (e) {
var touch = e.originalEvent.touches[0];
self.touchDistance.start = touch.pageX;
});
this.$moveElement.on('touchmove', function (e) {
var touch = e.originalEvent.touches[0],
distance = 0;
self.touchDistance.end = touch.pageX;
distance = self.touchDistance.end - self.touchDistance.start;
self.dragIt(distance);
});
With this, I've got a working solution, as we can see here http://jsbin.com/quhabi/2/edit
Thanks for looking, hope this is useful for someone else in the future!