Circular rotating/magnifying menu with jquery - javascript

I'm trying to make a menu that contains 5 items/icons with the selected one being in the center. Clicking to the left or right of this centered icon, rotates the menu left or right, wrapping round the edges and moving whichever item was closest to the edge back in through the opposite one. Clicking on the centered item takes you to its linked URL.
The menu should also magnify in a way similar to the OS X dock except the magnification levels are set based on position not mouseover.
I've made a diagram which is easier to understand than my ramblings.
(source: yfrog.com)
I've managed to cobble together a simple jQuery version, where the items swap positions as needed, but can't figure out how to animate this movement, especially the wrap around the edges part, and change size based on position.
I'm guessing my code is probably not the best either :)
The HTML is as follows:
<div id="nav">
<div id="leftnav"></div>
<div id="rightnav"></div>
<div id="navblock1" class="navblock">
one
</div>
<div id="navblock2" class="navblock">
two
</div>
<div id="navblock3" class="navblock">
three
</div>
<div id="navblock4" class="navblock">
four
</div>
<div id="navblock5" class="navblock">
five
</div>
And the JS:
function rotateNav(direction) {
var change = (direction=='left')?(-1):(+1);
$('div.navblock').each(function() {
oldPos = parseInt($(this).attr('id').substr(9));
newPos = oldPos+change;
if (newPos == 0)
newPos = 5;
else if (newPos == 6)
newPos = 1;
$(this).attr('id','navblock'+newPos);
});
}
$(document).ready(function(){
$("#leftnav").click(function() {
rotateNav('right');
});
$("#rightnav").click(function() {
rotateNav('left');
});
});
All the .navblock elements are absolutely positionned. The #leftnav and #rightnav elements also and they have a higher z-index so float above the items/icons.
I've looked at various jQuery plugins but none seem close to what I need.

Instead of changing id attributes (which you really shouldn't do in the first place) you can change CSS classes and use jQuery UI's switchClass() method to animate the rotation.
You would also have to do a bit of clone()ing to make it look like the edge navblocks have rotated around to the other side of the widget and some queue()/dequeue()ing to handle multiple clicks.
Working Demo:
http://jsbin.com/ovemu (editable via http://jsbin.com/ovemu/edit)
Full Source:
JavaScript
function rotateNav(direction) {
if (direction === 'left') {
var change = 1;
$('.navblock5').clone()
.removeClass('navblock5')
.addClass('navblock0')
.appendTo('#nav');
}
else {
var change = -1;
$('.navblock1').clone()
.removeClass('navblock1')
.addClass('navblock6')
.appendTo('#nav');
}
$('div.navblock').each(function() {
var oldClassName = this.className.split(' ')[1],
oldPos = parseInt(oldClassName.substr(8)),
newPos = oldPos + change;
$(this).switchClass(
oldClassName,
'navblock'+newPos,
'fast',
function () {
var animated = $('.navblock:animated').length;
if (newPos === 6 || newPos === 0) {
$(this).remove();
}
if (animated === 1) {
$('#nav').dequeue();
}
}
);
});
}
$(document).ready(function(){
$("#leftnav").click(function() {
$('#nav').queue(function(){rotateNav('right');});
});
$("#rightnav").click(function() {
$('#nav').queue(function(){rotateNav('left');});
});
});
CSS
#nav {
width: 580px; height: 120px;
position: relative; left: 150px;
overflow: hidden;
}
.navblock {
height: 100px; width: 100px;
position: absolute; top: 10px; z-index: 50;
background-color: grey;
}
.navblock0 { left: -110px; }
.navblock1 { left: 10px; }
.navblock2 { left: 120px; }
.navblock3 { left: 230px; width: 120px; height: 120px; top: 0;}
.navblock4 { left: 360px; }
.navblock5 { left: 470px; }
.navblock6 { left: 590px; }
#leftnav, #rightnav {
position: absolute; z-index: 100; height: 120px; width: 228px;
}
#leftnav { left: 0; }
#rightnav { right: 0; }
/*Uncomment the following to help debug or see the inner workings */
/*
#nav { border: 1px solid green; overflow: visible; }
#leftnav, #rightnav { border: 1px solid blue; }
*/
HTML
<div id="nav">
<div id="leftnav"></div>
<div id="rightnav"></div>
<div class="navblock navblock1">one</div>
<div class="navblock navblock2">two</div>
<div class="navblock navblock3">three</div>
<div class="navblock navblock4">four</div>
<div class="navblock navblock5">five</div>

Instead of doing this yourself and wasting time on getting this to work properly I suggest you use existing solutions ones. Here a few pointers (I guess many more can be found by using google
jQuery: Mac-like Dock
Mac-like icon dock (v2)
MAC CSS Dock Menu
jQuery mimicking the OS X dock
Simple OSX-like dock with jQuery
iconDock jQuery Plugin

You seem to be on the right track. One issue is that this line
oldPos = parseInt($(this).attr('id').substr(9));
Should use 8 in the substr:
oldPos = parseInt($(this).attr('id').substr(8));

Related

Child element is causing issues with parent onmousemove event listener

I have created a very simple example of my problem.
Fiddle Link
In the fiddle, I have created a div named parent containing 2 imgs (i take divs in the example for simplicity but in my project, these are images) and a controller div. I place the images on the top of each other by positioning 2nd image as absolute.
I want to clip the 2nd image using clip-path property whenever, I click and then drag the controller" over the parent div.
But the controller div is causing issue with parent mousemove event whenever cursor goes on controller div, mouseout event is fired on parent div causing glitch in animation.
Adding pointer-events: none property to controller div fix the glitch but it also takes away every kind of mouse interaction from the element and I want click and drag effect.
I want to create similar effect used in this website.
The problem seems to be that the positioning of the controller sometimes (not always) 'interferes' with the reading of offsetX on the parent. And the offset goes down (to 0 or up to about 10 in the given fiddle). Hence you get the flickering as the controller moves back and then up along again.
I cannot at the moment totally explain this, particularly since the controller is an absolutely positioned element.
However, one solution is to move the controller out of the parent.
UPDATE It is though possible to leave the controller in the parent if one ignores any mousemove within the controller (so we don't get readings of 0 to 10 for the offset when the mousemove is within the controller - ignore them and we'll get the event bubbling through to the parent and can then take a reading of offset).
_
<head>
<style>
* {
margin: 0;
padding: 0;
}
#parent {
width: 100%;
position: relative;
}
#img1, #img2 {
display: block;
width: 100%;
height: 200px;
pointer-events: none;
}
#img1 {
background: red;
}
#img2 {
background: green;
position: absolute;
top: 0;
left: 0;
}
#controller {
position: absolute;
top: 0;
left: 10px;
width: 10px;
height: 100%;
height: 200px;
background: black;
z-index: 1;
cursor: ew-resize;
/* pointer-events: none; */
}
</style>
</head>
<body>
<div id="parent">
<div id="img1"></div>
<div id="img2"></div>
<div id="controller"></div>
</div>
<h4>
Click and Drag the controller to clip the front image
</h4>
<!-- img1, img2 are images in my case so i named them as imgs -->
<script>
const parent = document.getElementById('parent'),
img2 = document.getElementById('img2'),
controller = document.getElementById('controller');
let pressed = false;
console.log(pressed)
parent.addEventListener('mousemove', (e) => {
if(!pressed) return;
if (e.target != parent) return;
img2.style.clipPath = `inset(0px 0px 0px ${e.offsetX}px)`;
controller.style.left = `${e.offsetX}px`;
});
// for testing purpose
/* parent.addEventListener('mouseout', (e) => {
console.log('mouse out is called');
}); */
controller.addEventListener('mousedown', (e) => {
pressed = true;
});
controller.addEventListener('mouseup', (e) => {
pressed = false;
});
</script>
</body>
const parent = document.getElementById('parent'),
img2 = document.getElementById('img2'),
controller = document.getElementById('controller');
let pressed = false;
parent.addEventListener('mousemove', (e) => {
if (pressed) {
img2.style.clipPath = `inset(0px 0px 0px ${e.clientX - parent.offsetLeft}px)`;
controller.style.left = `${e.clientX - parent.offsetLeft}px`;
}
});
controller.addEventListener('mousedown', (e) => {
pressed = true;
});
controller.addEventListener('mouseup', (e) => {
pressed = false;
});
#parent {
width: 100%;
position: relative;
}
#img1,
#img2 {
display: block;
width: 100%;
height: 200px;
pointer-events: none;
}
#img1 {
background: red;
}
#img2 {
background: green;
position: absolute;
top: 0;
left: 0;
}
#controller {
position: absolute;
top: 0;
left: 10px;
width: 10px;
height: 100%;
background: black;
z-index: 1;
cursor: ew-resize;
/* pointer-events: none; */
}
<div id="parent">
<div id="img1"></div>
<div id="img2"></div>
<div id="controller"></div>
</div>
<h4>
Click and Drag the controller to clip the front image
</h4>
The problem is, you used offsetX which defines the distance between the top left edge of your controller element. This means the distance is about 5px, your controller jumps to 5px from left, the distance is bigger now, the controller jumps back and so on.
The offsetX read-only property of the MouseEvent interface provides
the offset in the X coordinate of the mouse pointer between that event
and the padding edge of the target node.
So therefore you can use the difference between the mouse x-position and the x-position of parent for positioning your controller:
Instead use clientX which gets the mouse position relative to the window.
img2.style.clipPath = `inset(0px 0px 0px ${e.clientX - parent.offsetLeft}px)`;
controller.style.left = `${e.clientX - parent.offsetLeft}px`;
Top expression has following meaning:
<mouse x-position> - <distance between left screen edge and parent>

Div flickers on hover

I have read a lot of the questions on here but can't find one that fixes this. I have programmed a div to follow my cursor. I only want it to appear when the cursor is over #backgroundiv. I have got it working but it sometimes randomly flickers on chrome and disappears entirely on firefox. Even more randomly is it sometimes appears to work and then starts flickering. I have tried a variety of things from hover to mouseenter/mouseover but nothing seems to work.
What I want is for #newdot to appear when the cursor is over #backgroundiv and then follow the cursor around the div. Any help would be much appreciated.
//hide dot when leaves the page
$(document).ready(function() {
$("#backgroundiv").hover(function() {
$("#newdot").removeClass("hide");
}, function() {
$("#newdot").addClass("hide");
});
});
//div follows the cursor
$("#backgroundiv").on('mousemove', function(e) {
//below centres the div
var newdotwidth = $("#newdot").width() / 2;
$('#newdot').css({
left: e.pageX - newdotwidth,
top: e.pageY - newdotwidth
});
});
//tried below too but it doesn't work
/*$(document).ready(function(){
$("#backgroundiv").mouseenter(function(){
$("#newdot").removeClass("hide");
});
$("#backgroundiv").mouseout(function(){
$("#newdot").addClass("hide");
});
}); */
#backgroundiv {
width: 400px;
height: 400px;
background-color: blue;
z-index: 1;
}
#newdot {
width: 40px;
height: 40px;
background-color: red;
position: absolute;
z-index: 2;
}
.hide {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="newdot"></div>
<div id="backgroundiv"></div>
There is not issue but a logical behavior, when you hover on the blue div you trigger mouseenter so you remove the class and you see the red one BUT when you hover the red one you trigger mouseleave from the blue div thus you add the class and you hide the red one. Now the red is hidden you trigger again the mouseenter on the blue div and you remove the class again and the red div is shown, and so on ... this is the flicker.
To avoid this you can consider the hover on the red box to make the red box appear on its hover when you lose the hover from the blue one.
$(document).ready(function() {
$("#backgroundiv").hover(function() {
$("#newdot").removeClass("hide");
}, function() {
$("#newdot").addClass("hide");
});
});
//div follows the cursor
$("#backgroundiv").on('mousemove', function(e) {
//below centres the div
var newdotwidth = $("#newdot").width() / 2;
$('#newdot').css({
left: e.pageX - newdotwidth,
top: e.pageY - newdotwidth
});
});
#backgroundiv {
width: 400px;
height: 400px;
background-color: blue;
z-index: 1;
}
#newdot {
width: 40px;
height: 40px;
background-color: red;
position: absolute;
z-index: 2;
}
.hide {
display: none;
}
/* Added this code */
#newdot:hover {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="newdot">
</div>
<div id="backgroundiv">
</div>

Fix an element when it reaches the top of the screen using javascript and css

I have an element, that I wish to stick on top after it reaches the top of the screen.
<div id="HeaderWrapper">
...
<div id="Navigation">
Navigation
</div>
...
</div>
I am adding an event listener on scroll, which would call a function to check the posting of the element by using getBoundingClientRect() method. If the top or the y of the element is less then 0 relative to the viewport, then I would like to fix/stick the header. Again if its more than 0 then I would like to remove the fix position. In both the cases, I am adding and removing a class name of fixed_navbar which has the property of fix position.
document.addEventListener("scroll", function() {
const el = document.getElementById("Navigation");
let rect = el.getBoundingClientRect();
if (rect.top <= 0) {
el.classList.add("fixed_navbar");
} else {
el.classList.remove("fixed_navbar");
}
});
You can also the check the codepen demo.
When the position top of the element is more than zero it works fine. Also when scrolling down to the position where the element's top position is less than 0 it sticks to the page and has the fixed propery. But again when scrolling back to the position when the element's top is more than 0, the element still has the fixed propery and stick's to the top of the screen. How can I make the element stick to the top when it reaches the top of the screen and again when the element is below the top of the screen remove the fixed postion?
You can achieve this with CSS alone, by using:
position: sticky
When declaring position: sticky; you will also need to declare a top style (eg. top: 0;) to indicate at which point you want the element to become "stuck".
Working Example:
header {
height: 600px;
}
.navigation {
position: sticky;
top: 0;
margin-top: 150px;
}
<header>
<div class="navigation">Navigation</div>
</header>
Further Information:
position: sticky works in the following browsers:
https://caniuse.com/#feat=css-sticky
Try This
if (rect.top <= 0) {
In if condition you write rect.top < 0 that is wrong for your requirement
#Rounin provide an awesome solution. Although I fix your issue in JavaScript. you can check this
document.addEventListener("scroll", function() {
const el = document.getElementById("Navigation");
let rect = el.getBoundingClientRect();
if (rect.top <= 0) {
el.classList.add("fixed_navbar");
} else {
window.onscroll = function() {myFunction()};
function myFunction() {
if ( document.body.scrollTop < 100 ) {
el.classList.remove("fixed_navbar");
}
}
}
});
* {
margin: 0;
padding: 0;
}
#HeaderWrapper {
background: lightgrey;
height: 1500px;
}
.box {
background: skyblue;
width: 100%;
height: 100px;
}
#Navigation {
background: green;
}
.fixed_navbar {
position: fixed;
z-index: 1000;
width: 100%;
left: 0;
top: 0;
}
<div id="HeaderWrapper">
<div class="box"></div>
<div id="Navigation">
Navigation
</div>
</div>

How do I create an invisible scrollable area on an HTML page?

I want to trigger an event whenever the user scrolls up or down inside an invisible div (a 'scroller'). Imagine the below setup :
CSS
#scroller {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 50px;
}
#scroller div {
position: absolute;
top: 0px;
left: 0px;
height: 50000px;
width: 100%;
}
span {
position: absolute;
top: 20px;
left: 100px;
}
HTML
<div id="scroller"><div></div></div>
<span></span>
Javascript
var timeout;
$("#scroller").scroll(function ()
{
clearTimeout(timeout);
$('span').text('scrolling');
timeout = setTimeout(function ()
{
$('span').text('');
}, 1000);
});
Whenever the user scrolls inside the above div, the word "scrolling" should appear on the screen. You can play around with this fiddle : http://jsfiddle.net/f1hxndt4/4/
There are two problems with the above :
Scrolling inside the 'scroller' obviously needs to be infinite (up and down) - Currently it only allows a 50000px scroll.
The "scroller" needs to be invisible. Currently the scrollbars are visible.
Any suggestions would be much appreciated, thank you!
Here is the solution in case anyone is interested : http://jsfiddle.net/f1hxndt4/14/
CSS
#scroller{
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 50px;
overflow: hidden;
}
#scroller .parent{
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 100px;
overflow-x:hidden;
}
#scroller .child {
position: absolute;
top: 0px;
left: 0px;
height: 50000px;
width: 100%;
}
span {
position: absolute;
top: 20px;
left: 100px;
}
HTML
<div id="scroller">
<div class="parent">
<div class="child"></div>
</div>
</div>
<span></span>
Javascript
var timeout;
$("#scroller .parent").scroll(function ()
{
clearTimeout(timeout);
$('span').text('scrolling');
timeout = setTimeout(function ()
{
$('span').text('');
}, 1000);
});
Explanation :
You need to create a scrollable <div> : $('#scroller .parent') and then place that inside a narrower <div> : $('#scroller'). Set the overflow of the latter to 'hidden'.
That way the scrollbar on the right side of $('#scroller .parent') will not be visible anymore.
If you bind to the 'scroll' event, then you will need to make the area scrollable (which as you say, defeats the point of the what you're trying to acheive!). Instead, you need to listen for the events that would otherwise usually cause scrolling, such as listening for mousehweel events. You may also wish to listen for swipe events etc.
You can calculate scroll distance by using the wheelData property of the event to detemrine the scroll delta. (In Firefox and opera you will need to use the detail property instead.)
var onMouseWheelEvent = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll"
: "mousewheel";
var timeout;
$("#scroller").on(onMouseWheelEvent, function (e)
{
clearTimeout(timeout);
$('span').text('scrolling');
var scrollEvent = e.originalEvent;
var delta = scrollEvent.detail? scrollEvent.detail*(-120) : scrollEvent.wheelDelta
console.log(e.originalEvent.wheelDelta);
timeout = setTimeout(function ()
{
$('span').text('');
}, 1000);
});
Demo: http://jsfiddle.net/techsin/o2n2q5p4/
Improved: http://jsfiddle.net/techsin/o2n2q5p4/1/
This is similar to link you posted however it dosen't rely on scrolled up amount but creates its own amount relying on mousewheel data. I tried to solve your original problem instead.
if anything is unclear just ask: (no jquery used just for challenge)
var a=0, topSpeed = 20, deg=0;
window.addEventListener('mousewheel', function(e){
if (a<topSpeed) {
a = a + ((e.wheelDelta/1000) * topSpeed);
}
});
var img = document.getElementById('gear');
function animate() {
a = +(a*.95).toFixed(2);
if (Math.abs(a)<1) a=0;
deg = (deg+a) % 360;
img.style.transform = 'rotate('+deg+'deg)';
requestAnimationFrame(animate);
}
animate();

how to i get the image to center in my gallery

I have made a simple slider gallery for my site but have found that when I click next the image updates but it does not centre until I have done a full cycle of the images
how can i get the images to align from the start?
HERE IS THE JS FIDDLE > http://jsfiddle.net/8pScd/4
HTML
<div class="view_gallery">view gallery</div>
<div class="prev control"><<</div>
<div class="next control">>></div>
<div class="gallery">
</div>
<div class="overlay"></div>
CSS
.overlay{
display: none;
position: absolute; top: 0; right: 0; bottom: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.8);
z-index: 100;
}
.gallery{
z-index: 200;
padding: 10px;
position: absolute;
left: 50%;
background: #fff;
}
.control{
position: absolute;
top: 200px;
z-index: 300;
color: #fff;
text-transform: capitalize;
font-size: 2em;
cursor: pointer;
}
.prev{left: 0;}
.next{right:0;}
JQUERY
//images
var pics = new Array();
pics[0] = "cars.jpg";
pics[1] = "cats.png";
pics[2] = "dogs.png";
pics[3] = "bus.jpg"
//total amount of pictures to display
var pictot = pics.length-1;
var nxt = $(".next"),
prv = $(".prev"),
view = $(".view_gallery"),
gal = $(".gallery"),
overlay = $(".overlay"),
num = 0;
//view gallery
view.click(function(){
overlay.show();
gal.show();
// Start gallery off on the first image
gal.html('<img src="' + pics[0] + '" />');
});
nxt.click(function(){
// If on the last image set value to 0. Else add 1
if (num == pictot){num = 0;}else{num++;};
update();
});
prv.click(function(){
// If on first image set value to last image number. Else minus 1
if (num == 0){num = pictot;}else{num--;}
update();
});
function update () {
// update image with next/previous
gal.html('<img src="' + pics[num] + '" />');
//center image (not working very well)
var x = gal.width()/2;
gal.css("marginLeft", -x);
};
//hide
overlay.click(function(){
gal.hide();
$(this).hide();
});
The problem you have is that the "update" function is called immediately after clicking on prev/next. The image has not yet been loaded, so the code does not actually know the new gal.width yet. That's why it works after a full round: the images are now in the cache, and therefore already available.
The best solution would be to use javascript Image objects to preload the pictures; an easier way but possibly problematic is to use the 'load' event (it may not work well in all browsers).
You can align your gallery div with some simple css hack.
1)first define width. (you can define dynamic width with jquery).
2)add position:absolute;
3)add left:0 , right:0;
4)add margin:0 auto;
final code looks like this.
.gallery {
background: none repeat scroll 0 0 #FFFFFF;
left: 0;
margin: 0 auto !important;
padding: 10px;
position: absolute;
right: 0;
width: 600px;
z-index: 200;
}
your math is wrong, look at this example http://jsfiddle.net/8pScd/6/
i've just need to change your math at
var x = $('body').width()/2 - gal.width()/2;
gal.css("margin-left", x + 'px');
and i removed this line at your css
left: 50%;
.gallery{
z-index: 200;
padding: 10px;
position: absolute;
background: #fff;
}
Knowing that .gallery is 920px wide, set left: 50%; margin-left: -470px. Also remove the line in javascript which updates margin-left of the gallery container - gal.css("marginLeft", -x);

Categories

Resources