Drag an element using JavaScript (Translating the position CSS3) - javascript

I am working on a small task, where I have to drag (translate) an element anywhere in the document freely. I have done the basic work but confused about the current position of the mouse. Because when I start dragging the element, the mouse position is not on the spot where the mousedown occurs.
Simply, I want the position of the mouse to stay on where I clicked on the box.
Here's the JSFiddle link.

Well you need to calculate the width and height of the element in order to keep the cursor in its center, note that now it's working with any width and height values, here i have added an animation just resizing width and height to see that always we get the center of the element
let target = document.querySelector(".drag");
function onDrag(e) {
// we could make them global variables instead
const {width, height} = window.getComputedStyle(target);
target.style.transform = `translate(${e.clientX - +width.replace("px", "") / 2}px, ${e.clientY - +height.replace("px", "") / 2}px)`;
}
function onLetGo() {
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', onLetGo);
}
function onGrab() {
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', onLetGo);
}
target.addEventListener('mousedown', onGrab);
* {
box-sizing: border-box;
}
.drag{
width: 100px;
height: 100px;
border: 1px solid black;
background: blue;
cursor: pointer;
position: fixed;
animation-name: resize;
animation-duration: 4s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
#keyframes resize {
0% {width: 100px}
25% {height: 150px}
50% {width: 150px}
100% {height: 100px}
}
<div class="drag"></div>
Edited to answer the comment of the OP
Thanks for the answer, it works fine. Is there any way, we can drag the div from any place where we click the div?
So now you want to drag the element from the clicked point and not from its center you can subtract event.offsetX from event.clientX to get the correct cursor position and the same for the y axis, and make sure there is no margin or padding for the containers, in this example I have removed the margin and padding from the HTML and BODY elements
let target = document.querySelector(".drag"), x = 0, y = 0;
function onDrag(e) {
// we use the coords of the mousedown event
target.style.transform = `translate(${e.clientX - x}px, ${e.clientY - y}px)`;
}
function onLetGo() {
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', onLetGo);
}
function onGrab(e) {
// we store the point of click(coords)
x = e.offsetX, y = e.offsetY;
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', onLetGo);
}
target.addEventListener('mousedown', onGrab);
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
}
.drag{
width: 100px;
height: 100px;
border: 1px solid black;
background: blue;
cursor: pointer;
position: fixed;
}
<div class="drag"></div>
Well because transition is affected by margin, padding and other flow control rules you can avoid using it and just use the left and top rules to properly position your element like this
let target = document.querySelector(".drag"), x = 0, y = 0;
function onDrag(e) {
// use the `left` and `top` rules to properly position your element, so
// you no more care about other flow affecting rules
target.style.left = `${e.clientX - x}px`;
target.style.top = `${e.clientY - y}px`;
}
function onLetGo() {
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', onLetGo);
}
function onGrab(e) {
x = e.offsetX, y = e.offsetY;
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', onLetGo);
}
target.addEventListener('mousedown', onGrab);
.drag {
width: 100px;
height: 100px;
border: 1px solid black;
background: blue;
cursor: pointer;
position: fixed;
}
<div class="drag"></div>

Related

Use absolute coordinates with transform translate

I can make a box that follows the mouse with the following.
document.addEventListener('mousemove', (e) => {
document.documentElement.style.setProperty('--mouse-x', e.clientX + 'px');
document.documentElement.style.setProperty('--mouse-y', e.clientY + 'px');
});
.box {
width: 50px;
height: 50px;
background-color: blue;
transform: translate(calc(var(--mouse-x) - 50%), calc(var(--mouse-y) - 50%));
}
<div class="box"></div>
but as soon as the element is not positioned at the top left, it breaks.
document.addEventListener('mousemove', (e) => {
document.documentElement.style.setProperty('--mouse-x', e.clientX + 'px');
document.documentElement.style.setProperty('--mouse-y', e.clientY + 'px');
});
.box {
width: 50px;
height: 50px;
margin-left: 150px;
background-color: blue;
transform: translate(calc(var(--mouse-x) - 50%), calc(var(--mouse-y) - 50%));
}
<div class="box"></div>
How can I use absolute coordinates with a transform? I don't want to use left/top/position: fixed/absolute because I need to preserve the position of the element in the flow.
I could use JavaScript to grab the central position and then use that infomation to get the correct center.
document.addEventListener('mousemove', (e) => {
document.documentElement.style.setProperty('--mouse-x', e.clientX + 'px');
document.documentElement.style.setProperty('--mouse-y', e.clientY + 'px');
});
window.addEventListener('load', (e) => {
const box = document.querySelector('.box');
const rect = box.getBoundingClientRect();
box.setAttribute('style', `
--center-x: ${rect.left + (rect.width / 2)}px;
--center-y: ${rect.top + (rect.height / 2)}px;
`);
});
.box {
width: 50px;
height: 50px;
margin-left: 150px;
background-color: blue;
transform: translate(calc(var(--mouse-x) - var(--center-x)), calc(var(--mouse-y) - var(--center-y)));
}
<div class="box"></div>
This works, but it's not ideal and easily broken if anything else in the page changes. It also slows down with more elements, and I'd like it to be as fast as possible. It there a better way to do this? I'm fine using CSS/Vanilla JS.
You don't have to use translate() please I would recommend you to use the left and top properties in CSS. They can help you position an element based on coordinates.
window.addEventListener('load', (e) => {
const box = document.querySelector('.box');
const rect = box.getBoundingClientRect();
document.addEventListener('mousemove', (e) => {
box.style.left = e.pageX + 'px';
box.style.top = e.pageY + 'px';
});
});
.box {
width: 50px;
height: 50px;
position:absolute;
transform:translate(-50%,-50%);
background-color: blue;
}
<div class="box"></div>
The transform: translate() property works relative to the size of the box but the left and top properties don't. It can also be much faster in some cases because in your code there was a lot of calculation going on. Whereas, this is straightforward.
Here is a solution using the requestAnimationFrame() function. The requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint. For higher refresh rate monitors, this function will be run more the number of times to match the refresh rate.
More information here
Here is the working solution:
const box = document.querySelector('.box');
const rect = box.getBoundingClientRect();
let mouseX = 0;
let mouseY = 0
document.addEventListener('mousemove', (e) => {
mouseX = e.pageX + 'px';
mouseY = e.pageY + 'px';
})
function mouseMove() {
box.style.left = mouseX;
box.style.top = mouseY;
requestAnimationFrame(mouseMove)
};
mouseMove()
.box {
width: 50px;
height: 50px;
position: absolute;
transform: translate(-50%, -50%);
background-color: blue;
}
<div class="box"></div>

How to scale a container keeping its bottom right corner fixed?

I have a red container which initially is at bottom right of black container. I have a scale function that gradually scales the container. I want to make the bottom right position of red container to be fixed and scale it towards top left. How can I do that?
const box = document.getElementById("box")
const initHeight = 200
const initWidth = 200
const centerX = initWidth / 2
const centerY = initHeight / 2
function transform(scale, translate) {
if (translate) {
translate[0] = -centerX + translate[0]
translate[1] = -centerY + translate[1]
}
box.style.transform = `scale(${scale})${
translate ? ` translate(${translate.map((x) => x + "px").toString()})` : ""
}`
}
let initX = initWidth
let initY = initHeight
let scaleVal = 0.5
transform(scaleVal, [initX, initY])
function scale() {
scaleVal = scaleVal + 0.01
transform(scaleVal, [
initX - scaleVal * initWidth,
initY - scaleVal * initHeight
])
if (scaleVal <= 1) {
setTimeout(() => {
requestAnimationFrame(scale)
}, 50)
}
}
scale()
* {
box-sizing: border-box;
}
.box {
height: 200px;
width: 200px;
background-color: black;
position: absolute;
}
.box:nth-child(2) {
background-color: red;
}
<div id="app">
<div class="box"></div>
<div class="box" id="box"></div>
</div>
To lock the bottom right corner of the red box to the bottom right of the black box this snippet does two things: positions red box right bottom relative to the parent app container and sets the transform-origin to that spot too (normally transform origin is at the center of an element). It then uses a CSS animation to expand the red box and contract it again using scale.
This method does not need JS as it is a simple scaling transform, but of course some of the subtleties of the original transformations are lost because of tying the corner down.
* {
box-sizing: border-box;
}
body {
position: relative;
width: 100vw;
height: 100vh;
}
#app {
position: absolute;
height: 200px;
width: 200px;
}
.box:nth-child(1) {
height: 200px;
width: 200px;
background-color: black;
position: absolute;
top: 0;
left: 0;
}
#box {
background-color: red;
width: 100px;
height: 100px;
position: absolute;
bottom: 0;
right: 0;
transform-origin: right bottom;
animation: scale 5s 1 ease-in-out;
}
#keyframes scale {
0% {
transform: scale(1);
}
50% {
transform: scale(2);
}
100% {
transform: scale(1);
}
}
<div id="app">
<div class="box"></div>
<div class="box" id="box"></div>
</div>
Okay so I finally figured it out,
const box = document.getElementById("box")
let scale = 0
const initWidth = 50
const initHeight = 50
function fixed(num, fix = 1) {
return Number(parseFloat(num).toFixed(fix))
}
function scaleBox() {
const [x, y] = [
fixed((initWidth - scale * initWidth) / 2),
fixed((initHeight - scale * initHeight) / 2)
]
box.style.transform = `translate(${x}px, ${y}px) scale(${scale})`
scale = scale + 0.1
if (scale < 1) {
setTimeout(() => {
requestAnimationFrame(scaleBox)
}, 500)
}
}
scaleBox()
* {
box-sizing: border-box;
}
.box {
height: 50px;
width: 50px;
background-color: black;
position: absolute;
}
.box:nth-child(2) {
background-color: red;
transform: translate(0, 0) scale(0);
}
<div id="app">
<div class="box"></div>
<div class="box" id="box"></div>
</div>
Explanation
The trick is to translate the container in such a way that when its scaled after the translation, it always places itself in the bottom right of purple container.
To figure out the translation amount, let's first scale the container to 0.5 without any translation. It looks like this,
As you can see the container's width is 25 as 0.5(scale) * 50(init_width)=25 and position from container from all sides(top left, bottom left, top right, bottom right) will be (25/2, 25/2)=(12.5,12.5) since the container is scaled equally from all sides.
Since the position from bottom right is (12.5,12.5), we need to translate the container to (+12.5,+12.5) and then scale it to exactly place it at bottom right.
You can achieve many things using display:flex, it's great!
This is how I would approach your problem:
const handleClick = () => {
const blackDiv = document.getElementById("black-div");
const redDiv = document.getElementById("red-div");
let widthRatio = 0;
let heightRatio = 0;
const scaleUpTimer = setInterval(() => {
if (widthRatio === 1 || heightRatio === 1) clearInterval(scaleUpTimer);
widthRatio = redDiv.offsetWidth / blackDiv.offsetWidth;
heightRatio = redDiv.offsetHeight / blackDiv.offsetHeight;
redDiv.style.width = widthRatio * 100 + 2 + "%";
redDiv.style.height = heightRatio * 100 + 2 + "%";
}, 10);
};
#black-div {
width: 200px;
height: 200px;
background-color: rgb(0, 0, 0);
display: flex;
align-items: flex-end;
justify-content: flex-end;
}
#red-div {
background-color: red;
width: 50%;
height: 50%;
}
<div id='black-div'>
<div id='red-div' onclick={handleClick()}></div>
</div>
EDIT: I used onclick here but obviously you would have to handle the situations where someone clicks the red square and its already scaled up to avoid setting unnecessary timers. Or you could just call the function directly, without having to click anything.

move a large div opposite to mouse movement

I have a table full of data that tends to be larger than the screen.
I put the table in a DIV and set the "overflow" to "auto" in CSS
div.scrolling-comps {
width : 970px;
height : 800px;
overflow : auto;
}
So the DIV can be scrolled up/down, left right using the browser's built-in scroll bars.
Problem is, the table can be WAAY bigger than the screen. And while the mousewheel will scroll it up/down, scrolling left/right is a pain in the hooch.
So, looking for a javascript/jquery or CSS way to scroll the div NATURALLY.
In other words, when someone viewing the huuuge table moves their mouse to the right, the DIV goes to the left (thus scrolling without using the scroll bars).
Something similar to this, but instead of following the mouse, the div would move opposite the mouse...
window.onload = function() {
var bsDiv = document.getElementById("box-shadow-div");
var x, y;
// On mousemove use event.clientX and event.clientY to set the location of the div to the location of the cursor:
window.addEventListener('mousemove', function(event) {
x = event.clientX;
y = event.clientY;
if (typeof x !== 'undefined') {
bsDiv.style.left = x + "px";
bsDiv.style.top = y + "px";
}
}, false);
}
#box-shadow-div {
position: fixed;
width: 1000px;
height: 800px;
border-radius: 0%;
background-color: black;
box-shadow: 0 0 10px 10px black;
top: 49%;
left: 48.85%;
}
<div id="box-shadow-div"></div>
The example you have about using the mouse position is interesting... But it is not what you need to achieve what you described.
In fact... What you need to know is the "ratio" between the div wrapping the table and its scrollWidth
Then, using the X position of the mouse, you can apply a scroll to the div in order to make it "move".
I used jQuery to do it using very few lines.
// Just to fake a big table
var fakeCell = $("<td>Some data</td>");
for(i=0;i<100;i++){
var fakeRow = $("<tr>");
for(k=0;k<50;k++){
fakeRow.append(fakeCell.clone().append(" "+k));
}
$("#test").append(fakeRow.clone());
}
// ---------------------------------------------------
// Calculate the "ratio" of the box-div width versus its scrollable width
var ratio = $("#box-div")[0].scrollWidth / $("#box-div").width();
console.log("Ratio: "+ratio);
// Scroll left/rigth based on mouse movement
$(window).on("mousemove", function(e){
var X = ratio * e.pageX;
// Scroll the div using the mouse position multiplyed by the ratio
$("#box-div").scrollLeft(X);
});
td{
white-space: nowrap;
border: 1px solid black;
}
#box-div{
overflow:auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div id="box-div">
<table id="test">
</table>
</div>
</body>
So while the user moves the mouse over the div's width, you apply a scroll multiplied by the ratio... The effect is the user can scroll it all from the most left to most right ends easilly.
How about this?
wrap a table in div (i.e. parent-div) which is relatively positioned
Give position absolute to the target div.
And change left & top position of target div on mousemove event.
window.onload = function() {
var bsDiv = document.getElementById("box-shadow-div");
var x, y;
// On mousemove use event.clientX and event.clientY to set the location of the div to the location of the cursor:
window.addEventListener('mousemove', function(event) {
x = event.clientX;
y = event.clientY;
if (typeof x !== 'undefined') {
bsDiv.style.left = -x + "px";
bsDiv.style.top = -y + "px";
}
}, false);
}
.parent-div {
position: relative;
}
#box-shadow-div {
position: absolute;
width: 1000px;
height: 800px;
border-radius: 0%;
background-color: black;
box-shadow: 0 0 10px 10px black;
top: 0;
left: 0;
}
<div class="parent-div">
<div id="box-shadow-div"></div>
</div>
Have you tried changing x to -x? this will technically "invert" the effect.
window.onload = function() {
var bsDiv = document.getElementById("box-shadow-div");
var x, y;
// On mousemove use event.clientX and event.clientY to set the location of the div to the location of the cursor:
window.addEventListener('mousemove', function(event) {
x = event.clientX;
y = event.clientY;
if (typeof x !== 'undefined') {
bsDiv.style.left = -x + "px";
bsDiv.style.top = -y + "px";
}
}, false);
}
#box-shadow-div {
position: fixed;
width: 1000px;
height: 800px;
border-radius: 0%;
background-color: black;
box-shadow: 0 0 10px 10px black;
top: 49%;
left: 48.85%;
}
<div id="box-shadow-div"></div>

Object with fixed Y-axis that follows the mouse on X-axis

I need help for an effect I'm trying to create: I made a CSS triangle and I want it to be fixed on the Y-axis but follow the mouse on his X-axis (didn't you read the title ?!). If it's not clear, I want it to move only to the left/right but not up/down. I managed to apply a js script I found on the internet to my triangle but I can't figure out how to change it to stop it from moving on the Y-axis. When I try to change anything, the whole thing doesn't move anymore. Can some one help me ?
// Here get the Div that you want to follow the mouse
var div_moving = document.getElementById('div_moving');
// Here add the ID of the parent element
var parent_div = 'parent_div';
// object to make a HTML element to follow mouse cursor ( http://coursesweb.net/ )
var movingDiv = {
mouseXY: {}, // will contain the X, Y mouse coords inside its parent
// Get X and Y position of the elm (from: vishalsays.wordpress.com/ )
getXYpos: function(elm) {
x = elm.offsetLeft; // set x to elm’s offsetLeft
y = elm.offsetTop; // set y to elm’s offsetTop
elm = elm.offsetParent; // set elm to its offsetParent
//use while loop to check if elm is null
// if not then add current elm’s offsetLeft to x, offsetTop to y and set elm to its offsetParent
while(elm != null) {
x = parseInt(x) + parseInt(elm.offsetLeft);
y = parseInt(y) + parseInt(elm.offsetTop);
elm = elm.offsetParent;
}
// returns an object with "xp" (Left), "=yp" (Top) position
return {'xp':x, 'yp':y};
},
// Returns object with X, Y coords inside its parent
getCoords: function(e) {
var xy_pos = this.getXYpos(e.target);
// if IE
if(navigator.appVersion.indexOf("MSIE") != -1) {
var standardBody = (document.compatMode == 'CSS1Compat') ? document.documentElement : document.body;
x = event.clientX + standardBody.scrollLeft;
y = event.clientY + standardBody.scrollTop;
}
else {
x = e.pageX;
y = e.pageY;
}
x = x - xy_pos['xp'];
y = y - xy_pos['yp'];
return {'xp':x, 'yp':y};
}
};
// registers 'mousemove' event to parent_div
document.getElementById(parent_div).addEventListener('mousemove', function(e){
mouseXY = movingDiv.getCoords(e);
div_moving.style.left = mouseXY.xp + 8 +'px';
div_moving.style.top = mouseXY.yp - 8 +'px';
});
#parent_div {
position: relative;
width: 100%;
height: 800px;
margin: 1em auto;
border; 1px solid #333;
background: #fefebe;
}
#div_moving {
position: absolute;
width: 41em;
height: 31em;
margin: 0;
border: 1px solid #33f;
background: #88ee99;
overflow:hidden;
}
.container {
width: 37.5em;
height: 37.5em;
position: relative;
border-top: 20px solid #e74c3c;
left:3%;
}
.triangle {
position: relative;
margin: auto;
top: -20em;
left: 0;
right: 0;
width:31em;
height:31em;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-o-transform: rotate(45deg);
-ms-transform: rotate(45deg);
border-right: 20px solid #e74c3c;
border-bottom: 20px solid #e74c3c;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parent_div">
<div id="div_moving">
<div class="container">
<div class="triangle"></div>
</div>
</div>
Content in parent ...
</div>
I just reformatted a little, then commented one line and it's working in Chrome on my machine. Is this what you're looking for?
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script language="javascript">
// object to make a HTML element to follow mouse cursor ( http://coursesweb.net/ )
var movingDiv = {
mouseXY: {}, // will contain the X, Y mouse coords inside its parent
// Get X and Y position of the elm (from: vishalsays.wordpress.com/ )
getXYpos: function(elm) {
x = elm.offsetLeft; // set x to elm’s offsetLeft
y = elm.offsetTop; // set y to elm’s offsetTop
elm = elm.offsetParent; // set elm to its offsetParent
//use while loop to check if elm is null
// if not then add current elm’s offsetLeft to x, offsetTop to y and set elm to its offsetParent
while(elm != null) {
x = parseInt(x) + parseInt(elm.offsetLeft);
y = parseInt(y) + parseInt(elm.offsetTop);
elm = elm.offsetParent;
}
// returns an object with "xp" (Left), "=yp" (Top) position
return {'xp':x, 'yp':y};
},
// Returns object with X, Y coords inside its parent
getCoords: function(e) {
var xy_pos = this.getXYpos(e.target);
// if IE
if(navigator.appVersion.indexOf("MSIE") != -1) {
var standardBody = (document.compatMode == 'CSS1Compat') ? document.documentElement : document.body;
x = event.clientX + standardBody.scrollLeft;
y = event.clientY + standardBody.scrollTop;
}
else {
x = e.pageX;
y = e.pageY;
}
x = x - xy_pos['xp'];
y = y - xy_pos['yp'];
return {'xp':x, 'yp':y};
}
};
$(document).ready(function() {
// Here get the Div that you want to follow the mouse
var div_moving = document.getElementById('div_moving');
// Here add the ID of the parent element
var parent_div = 'parent_div';
// registers 'mousemove' event to parent_div
document.getElementById(parent_div).addEventListener('mousemove', function(e){
mouseXY = movingDiv.getCoords(e);
div_moving.style.left = mouseXY.xp + 8 +'px';
//div_moving.style.top = mouseXY.yp - 8 +'px';
});
});
</script>
<style>
#parent_div {
position: relative;
width: 100%;
height: 800px;
margin: 1em auto;
border; 1px solid #333;
background: #fefebe;
}
#div_moving {
position: absolute;
width: 41em;
height: 31em;
margin: 0;
border: 1px solid #33f;
background: #88ee99;
overflow:hidden;
}
.container {
width: 37.5em;
height: 37.5em;
position: relative;
border-top: 20px solid #e74c3c;
left:3%;
}
.triangle {
position: relative;
margin: auto;
top: -20em;
left: 0;
right: 0;
width:31em;
height:31em;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-o-transform: rotate(45deg);
-ms-transform: rotate(45deg);
border-right: 20px solid #e74c3c;
border-bottom: 20px solid #e74c3c;
}
</style>
</head>
<body>
<div id="parent_div">
<div id="div_moving">
<div class="container">
<div class="triangle"></div>
</div>
</div>
Content in parent ...
</div>
</body>
</html>
Differences:
Load the JQuery script first
Process the listener in a ready function so all the content has been loaded
Comment out the Y positioning
EDIT: I found a solution to my problem.
So here the problems and what I did:
I wanted the object to move only on X-axis and not Y: IgnusFast found out the line to delete was "div_moving.style.top = mouseXY.yp - 8 +'px';"
I wanted it to stop staggering when the mouse passed over it: deleted "parseInt(x) +" in "while(elm != null) {x = parseInt(x) + parseInt(elm.offsetLeft); elm = elm.offsetParent;}" (makes the div stay where it is when not sure.
I wanted it to center with the mouse instead of being on its right: original was " div_moving.style.left = mouseXY.xp + 8 +'px';" wich made it go 8 pixels to the right of the current mouse's coordinates so I just used a negative number and place like this :" div_moving.style.left = mouseXY.xp + -350 +'px';"

Modifying cursor property for select portion of an element

I have this HTML that renders a simple arrow sign pointing towards the right:
<!DOCTYPE html>
<html>
<head>
<style>
div { width: 0px; height: 0px; border-left: 20px solid black; border-top: 20px solid transparent; border-bottom: 20px solid transparent; border-right: 20px solid transparent; position: absolute; left: 35px; top: 53px; cursor: pointer; }
</style>
<body>
<div></div>
</body>
</html>
If you hover of it, the cursor turns to pointer. But because it is actually a square div, the cursor turns pointer even if you are just outside the arrow within the perimeter of the div.
So I wrote this Javascript addition such that the cursor turns pointer only when the mouse is hovering over that arrow. For this purpose, I figured the coordinates of the three vertices of the triangle from Firebug ((35,53),(55,73),(35,93) clockwise from top). Then I check whether the point in question lies inside the triangle formed by these 3 vertices. This I do by checking whether the point and the opposite vertex for each edge lies on the same side of that edge or not (if they do, the product of the values obtained by substituting the coordinates of that point for x and y in that equation will be positive).
<!DOCTYPE html>
<html>
<head>
<style>
div { width: 0px; height: 0px; border-left: 20px solid black; border-top: 20px solid transparent; border-bottom: 20px solid transparent; border-right: 20px solid transparent; position: absolute; left: 35px; top: 53px; }
.hoverclass { cursor: pointer; }
</style>
<script src="jquery.js">
</script>
<script>
$(document).ready(function(){
$("div").click(function(e) { alert(e.pageX + " " + e.pageY); });
function l1(x,y) { return y - x - 18; }
function l2(x,y) { return x+y-128; }
function l3(x,y) { return x-35; }
$("div").hover(function(e) {
var x = e.pageX;
var y = e.pageY;
if (l1(x,y)*l1(35,93) >= 0 && l1(x,y)*l1(35,93) >= 0 && l1(x,y)*l1(35,93) >= 0 ) {
$(this).addClass('hoverclass');
}
else { $(this).removeClass('hoverclass'); }
},
function() {
$(this).removeClass('hoverclass');
});
});
</script>
<body>
<div></div>
</body>
</html>
However, the results are not predictable. Sometimes the cursor turns pointer within the triangle only, sometimes outside as well (just as before), and sometimes not at all. I suspect that this is probably due to the hover function working overtime, that may be temporarily hanging the script. Is there any other way to achieve this?
This could be done using HTML5 canvas. Basic idea is to check for pixel color on mousemove on canvas element. This way, your element can be of any form as you wish. Of course, you should make some optimization of following code:
SEE WORKING DEMO
function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}
// set up triangle
var example = document.getElementById('example');
var context = example.getContext('2d');
context.fillStyle = '#000';
context.strokeStyle = '#f00';
context.lineWidth = 1;
context.beginPath();
// Start from the top-left point.
context.moveTo(10, 10); // give the (x,y) coordinates
context.lineTo(60, 60);
context.lineTo(10, 120);
context.lineTo(10, 10);
// Done! Now fill the shape, and draw the stroke.
// Note: your shape will not be visible until you call any of the two methods.
context.fill();
context.stroke();
context.closePath();
$('#example').mousemove(function(e) {
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
var coord = "x=" + x + ", y=" + y;
var c = this.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
if(p[3]!='0') $(this).css({cursor:'pointer'});
else $(this).css({cursor:'default'});
});
You'd better use CSS instead. With :before and :after pseudo classes you can do magic. Check out this Pure CSS GUI icons by Nicolas Gallagher.
If you use any CSS pre-processor, these icons can be wrapped up as a mixin, this way required properties can be assigned like this:
#icon > .close(16px, #fff, #E83921);
You can make any shape have cursor pointer with CSS only. The idea is to rotate wrapper container which has overflow: hidden (you can have several of them depending on the shape you need). In case of OP problem this code does a trick:
<div class="arrow"><i></i></div>
.arrow {
margin: 100px;
border_: 1px red solid;
width: 40px;
height: 40px;
overflow: hidden;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.arrow i {
height: 65px;
width: 65px;
background-color: green;
content: '';
display: block;
cursor: pointer;
margin: -35px 0 0 11px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
See this demo: http://cssdesk.com/PaB5n
True that this requires CSS transform support so it's not cross browser.

Categories