Snap cursor to specific point of draggable element - javascript

I am dragging thumbnails from one DIV to another and attempting to imitate the "snap-to-a-grid" feature used in AutoCAD , Adobe Illustrator, many other graphics-editing software, etc.
Question 1:
How could I get the pointer to snap to the 0,0 position (x and y position) of the image I am clicking for dragging, regardless of where the pointer was on the image when clicked?
When I click to drag an image, the pointer sticks to where the pointer was when clicked and the coordinates I am going to track will be for the pointer.
I tried this:
cursor: url(mycustomcursor.png) 0 0 , auto;
and the custom pointer appears but doesn't snap to 0,0 as hoped.
Question 2:
How can I get my image to stick precisely where dropped in a DIV and return the offset in pixels from the top/left of the DIV it is being dropped into?
I don't know if the cursor position when dragging is relevant but when I drop my image with the following script the image shifts twice the distance I expect of in other words the offsetX value, doubled. I have added the script below and had to edit out a bunch of (hopefully) non-relevant script like CSS for colors, borders, etc. I am also only working with the X-coordinates for simplicity in testing.
<div style='overflow:scroll;text-align:left;position:absolute;width:90%; height:180px;' ondrop='drop(event)' ondragover='allowDrop(event)'>
<img id='image_1234' src='image_path/image.png' style='position:absolute;height:100px;' draggable='true' ondragstart='drag(event)'>
</div>
<div id='panel_frame' style='width:600px;height:300px;' ondrop='drop(event)' ondragover='allowDrop(event)'>
</div>
function allowDrop(evAllow) {
evAllow.preventDefault();
}
function drag(evDrag) {
evDrag.dataTransfer.setData('text', evDrag.target.id);
}
function drop(evDrop) {
evDrop.preventDefault();
var dropData = evDrop.dataTransfer.getData('text',evDrop.id);
evDrop.target.appendChild(document.getElementById(dropData));
var offsetLeft = evDrop.clientX + 'px';
document.getElementById(dropData).style.left = offsetLeft;
}
I am testing in Firefox. Any help is appreciated. Thanks!

Related

How do you add an annotation to a website without altering the layout?

I'm working on a hobby project similar to markup.io where you can load in any website and annotate it. I can't figure out how to add an annotation that behaves like it does in markup.io:
Doesn't interrupt the styling or layout of the website you are annotating
The annotation keeps the correct position when scrolling and resizing the window
From what I can see they place an absolute positioned div inside the element that you clicked on. From my understanding by reading the docs that div would position itself based on the closest positioned ancestor. How would you calculate the correct top and left values to position the annotation to where the user clicked? Is there a better way to do this?
I'm using React if that matters.
Things that I have tried:
Append the following bit of html to the element that was clicked:
<div style="width:0px; height:0px; position:relative;">
<div style="width:50px;height:50px;position:absolute; ">this is the annotation </div>
</div>
Problem: This would mess with the page layout because of the relative positioned div that is not ignored by the document flow.
Create fixed overlay over the entire page. Get the css selector of the clicked element. Draw annotation on the fixed overlay at the x,y position of the element.
Problem: Whenever the user would scroll or resize the window the annotation would need to be redrawn at the new position of the element. I used getBoundintClientRect to get the new position and this would cause a reflow and caused the whole website to have severe perfomance issues when dealing with 100+ annotations.
Hopefully someone can point me in the right direction!
The general idea is as follows:
Find the parent of the element that you clicked on
Check if they are positioned (anything other than static)
If it is static search for the closest element that is positioned.
Set the new badge/annotation top and left position to that of the mouse minus the top and left of the element that you're going to append it to (in this case called parent).
Also account for the width and height by subtracting half of each to perfectly center your annotation.
// In my case I put the webpage in an Iframe. If this is your own page
// you can just use document.
iframe.contentWindow.document.addEventListener(
'click',
(e: MouseEvent) => {
// step 1: find the parent.
let parent = e.target.parentElement;
let computedStyle = window.getComputedStyle(parent);
// step 2 & 3: Look up the first positioned element and make this the
// the element that you're going to append your badge/annotation to.
while (
computedStyle.position === 'static' &&
parent.parentElement !== null
) {
parent = parent.parentElement;
computedStyle = window.getComputedStyle(parent);
}
// style the annotation the way you want to
const badge = document.createElement('div');
const { top, left } = parent.getBoundingClientRect();
badge.style.position = 'absolute';
// step 4 and 5 get the mouse position through e.clientX and Y and
// subtract the appropriate value like below to place it exactly at the mouse position
badge.style.top = `${e.clientY - top - 5}px`;
badge.style.left = `${e.clientX - left - 5}px`;
badge.style.backgroundColor = 'red';
badge.style.width = '10px';
badge.style.height = '10px';
badge.style.borderRadius = '50%';
badge.style.zIndex = '9999';
parent.appendChild(badge);
}
);

jQuery: Move a DIV to left when its child element comes close to right edge of its parent element (type of auto-scrolling)

I'm currently building a somewhat basic video timeline. On this timeline are various media assets of variable duration and the width of the DIVs representing the assets reflect the duration, with 1 second being measured as 35px. So, for example, a 5 second asset would take up 175px in width on the timeline.
Because the timeline needs to be longer than the width of my usable space on the page, it needs to scroll horizontally. Instead of using an ugly standard scrollbar, I'm using a jQuery plugin scrollbar, which requires that the full-width DIV of the timeline sits inside another DIV that is the width of the usable area of the page and acts as a frame, with the inner DIV being absolutely positioned. When you move the scrollbar left or right it changes the "left" value of the inner DIV.
Having given that context, I now come to my problem. The assets on the timeline need be horizontally resizable to adjust their duration. I have that working using jQuery UI, but I need to make it so that when I drag the right edge of an asset near to the right edge of the outer DIV framing the timeline, the inner DIV of the timeline moves (basically scrolls) left and the width of the asset increases by 1 second (35px).
Even this last bit I have working to a certain degree, but not well enough. What I need is that when I drag far enough to the right, so that I'm within 35-70 pixels of the right edge of the framing DIV, the inner DIV timeline will move to the left, the width of the asset will increase, and this will keep happening until I move my mouse back towards the left.
The best example I can think of is like when you're selecting text in your browser and you drag past the bottom of the screen, the screen starts scrolling down and it keeps doing that till you move your mouse up.
Currently I'm trying to to this by drawing on the "resize" event of the jQuery UI resizable element, but the problem is that I can't get that continuous effect I was just talking about, I have to keep dragging my mouse further to the right rather than just keeping it still. And when I reach the right edge of the window I have to release the mouse button, move back over to the resizing handle and start dragging again.
Here's the function I was trying to write (FYI, a .mediaInstance is an asset on the timeline):
//Scroll Timeline when resized handle comes close to right edge
function timelineScroll() {
//console.log('running');
var mediaElement = $('#mediaTrack .mediaInstance.resizing');
var track = $('#horiz_container_inner');
//Determine location of right edge of the timeline viewport
var timeline = $('#horiz_container_outer');
var timelineOffset = timeline.offset();
var timelineLeft = timelineOffset.left;
var timelineRight = timelineLeft + $('#horiz_container_outer').width();
//Find right edge of current .mediaInstance
var instanceOffset = mediaElement.offset();
var instanceLeft = instanceOffset.left;
var instanceRight = instanceLeft + mediaElement.width();
if ( (timelineRight-instanceRight) < 35 ) {
var timelineCurrentLeft = Number(track.css('left').replace('px',''));
var timelineNewLeft = timelineCurrentLeft - 70;
track.css('left',timelineNewLeft);
mediaCurrentWidth = mediaElement.width();
mediaElement.width(mediaCurrentWidth+35);
if (currentMousePos.x > timelineRight) {
while (currentMousePos.x > timelineRight) {
var timelineCurrentLeft = Number(track.css('left').replace('px',''));
var timelineNewLeft = timelineCurrentLeft - 35;
track.css('left',timelineNewLeft);
mediaCurrentWidth = mediaElement.width();
mediaElement.width(mediaCurrentWidth+35);
}
}
}
}
You'll notice I even tried a loop at the end there based on the mouse position being farther right than the right edge of the framing DIV, but I didn't think it would work, and it didn't ... just seemed to put me in an infinite loop.
In any case, I'd really appreciate any help anyone can offer on this. I'm working on a project with a really short turnaround time and I've never really done any of this particular stuff before.
It turns out I solved my own problem. I just needed to use a setTimeout within the 'resize' function checking if the mouse was beyond the right edge of the framing DIV every 250 milliseconds and, if so, move the inner DIV left and increase the width of the asset. Here's what I used...
EDIT: It turns out my solution didn't work as I'd hoped, so I could use some help after all.
Here's the HTML:
<div id="horiz_container_outer">
<div id="horiz_container_inner" style="position: absolute; left: 0px; top: 0px; visibility: visible;">
<div id="horiz_container" style="width: 10500px;">
<div id="transitionTrack"></div>
<div id="mediaTrack" class="ui-sortable ui-droppable">
<div class="transitionBoxContainer first ui-droppable"></div>
<div class="mediaInstance" assetid="001" assettype="video" thumb="video1_thumb.jpg" style="display: block; width: 419px;" type="asset" duration="12">
<div class="mediaThumbnail" style="background-image: url(./images/assets/thumbnails/video1_thumb.jpg);"></div>
<div class="mediaInfo">
<div class="mediaFilename">video1.avi</div>
<div class="mediaDuration">12s</div>
<div class="mediaHandle"></div>
</div>
<div class="transitionBoxContainer ui-droppable"></div>
<div class="deleteButton"></div>
</div>
</div><!-- End of #mediaTrack -->
</div><!-- End of #horiz_container -->
</div><!-- End of #horiz_container_inner -->
</div><!-- End of #horiz_container_outer -->
And here's my code to make the mediaInstance on the timeline resizable, snapping to 35px increments:
//Watch timeline assets for click on resize handle
$("#mediaTrack").on('mousedown','.mediaInstance .mediaHandle', function(e) {
e.stopPropagation();
e.preventDefault();
//Find offset location of right edge of the timeline viewport
var timelineViewport = $('#horiz_container_outer');
var timelineViewportLeft = timelineViewport.offset().left;
var timelineViewportRight = timelineViewportLeft + timelineViewport.width();
//Assign track object to variable
var track = $('#horiz_container_inner');
var thisInstance = $(this).parents('.mediaInstance');
//Store the mouse position before we start moving
var startMousePos = e.pageX;
$(document).mousemove(function(e){
var currentInstanceWidth = thisInstance.width();
//Find right edge offset of current .mediaInstance
var instanceLeft = thisInstance.offset().left;
var instanceRight = instanceLeft + currentInstanceWidth;
if ( (e.pageX < (startMousePos-35)) || (e.pageX > (startMousePos+35)) ) {
if ( e.pageX < (startMousePos-35) ) {
thisInstance.width(currentInstanceWidth-35);
} else {
thisInstance.width(currentInstanceWidth+35);
}
startMousePos = e.pageX;
recalcDuration(thisInstance);
calcTotalDuration();
}
});
});
$(document).mouseup(function(e){
$(document).unbind('mousemove');
});
That works great for the actual resizing, but the problem I'm having is that when I move the mouse past the right edge of #horiz_container_outer, which acts as a frame for the timeline, I want the #horiz_container_inner DIV to start moving its left position to the left by increments of 35px while also continuing to resize the .mediaInstance div to make it 35px wider ... and I want both those things to happen every 0.25 seconds ... HOWEVER, I don't want the "scrolling" of #horiz_container_inner to continuously fire with the mousemoves. Once the mouse passes the right edge of #horiz_container_outer, I want some function to take over and start scrolling and resizing the .mediaInstance DIV at a set interval until the mouse once again moves left, past the right edge of #horiz_container_outer, at which point the original resizing shown above takes over again.
The problem is that I have no idea how to achieve this. I tried using a flag variable and conditional to tell me when my mouse is "in the zone", with inTheZone = false to begin with, running a conditional to run my initial code only when inTheZone == false, then setting it to true once the mouse enters the right area and having a setTimeout takeover to loop the scrolling and resizing. This worked to a certain degree, but the mouse position suddenly became unavailable so I couldn't tell when I moved outside the zone and the div just kept scrolling indefinitely.
Any ideas?

JQuery-UI Draggable: dragged element size

I'm trying to do the following: I have an icon on a page and when i drag it the icon changes. It has to not go beyond the containment area.
What I've tried is something like this:
$("#myElement").draggalble({
containment: "#myContainer",
start: function(event, ui) {
$(this).attr("width", "20");
$(this).attr("height", "20");
$(this).attr("src", "newSource.png");
}
});
The icon successfully changes and I can drag it.
The only problem is the new icon size. It is a little smaller than the original.
When I drag it near the boundaries of the container it stops too early, I can't reach container boundaries. Specifically I can't reach the upper and right boundaries but I can still reach the bottom and the left. I think this is because the draggable plugin calculates the icon limits using the size of the old icon.
Does anyone have some advice on how can I drag the new icon and reach the container boundaries?
Thanks in advance :)
To get this to work, I had to create a container object based on the smaller picture's dimensions.
Using the following options - parameters are the container div, small picture width and small picture height respectively:
containment: shrinkSize("#container",20,20),
and the function does the following:
function shrinkSize(frameId, smallerWidth, smallerHeight) {
var frameOffset = $(frameId).offset(),
frameHeight = $(frameId).height()-smallerWidth,
frameWidth = $(frameId).width()-smallerHeight;
return ([frameOffset.left, frameOffset.top, frameOffset.left+frameWidth, frameOffset.top+frameHeight]);
}
Fiddle here.
P.S. in the fiddle, the image being dragged reverts to original size when dropped. If this is not the intended behavior, just remove the stop option (with function) from the draggable definition.

image gallery /slide with zoom

I wanted to do something similar to this.
In this case when the user click in the image, this images is showed with 100% of the browser height, and the user can go to the next/previous image. When the user clicks again the image is showed in a bigger size(may be in the real size) and the user can go up and down in the image, but with out scroll, just moving the mouse.
What I want to do is when the user click the first time in the image go right to the last step: The biggest image with up and down synchronized with the mouse movement, and the possibility to go to the next image. In other words a mix with the features of the first and the second step of the original case.
Where I can see a tutorial, or a demo?? or how can I do the this??
Thanks
Basically, there are three parts to what you want to do.
Clicking on the image will show the image with respect to browser height
You can go to the next image while you are in this mode
Click on that image again will go into a supersize mode where your mouse position dictates what part of the image you are looking at
I'm not going to write a whole fiddle to demonstrate this because it's a decent amount of work but I can tell you the basic ideas.
With #1, when you click on the image, you will create a new div with a z-index of some high number (like 9999). The position would be fixed, and you will create
$(window).resize(function() {
var windowheight = $(window).height();
$("#imgdiv").css("height", windowheight);
});
Which will resize the image if the user decides to resize your window, this way it's always taking up the full height of your browser.
With #2, the arrows just create a new img tag. And the idea is something like
function loadnew() {
// create the new image
var newimg = "<img id='newimg'></img>"
$("#imgcontainer").append(newimg);
// make sure it has the same classes as the current img
// so that it's in the same position with an higher z-index
// then load the image
$("#newimg").addClass( "class1 class2" );
$("#newimg").css( "z-index", "+=1" );
$("#newimg").css( "opacity", 0 );
$("#newimg").attr("src", "url/to/img");
// animate the thing and then replace the src of the old one with this new one
$("#newimg").animate( {
opacity: 1;
}, 1000, function() {
$(oldimg).attr("src", $("#newimg").attr("src"));
});
}
Now with #3, you will size the image with respect to the width. The div fixed positioned. So again, you need a
$(window).resize(function() {
var windowwidth= $(window).width();
$("#imgdiv").css("width", windowwidth);
});
to make sure it's always taking up the whole screen. And for the mouse movement, you need to have a mousemove event handler
$("#superimgdiv").mousemove( function(e) {
// need to tell where the mouse is with respect to the window
var height = $(window).height();
var mouseY = e.pageY;
var relativepct = mouseY/height;
// change the position relative to the mouse and the full image height
var imgheight = $("superimg").height();
$("superimgdiv").css("top", -1*relativepct*imgheight);
});
And that's it. Of course I'm leaving out a bunch of details, but this is the general idea. Hopefully this can get you started. Good luck.

how do I move the image to half the div?

Ok so I have this Page and as you can see if you scroll down below the map you will see the logo and header for "Barinos Market"...if you click on the read more link the text expands ..which is exactly what i want. The problem I am trying to resolve is when the user clicks read more I need the barinos market image on the left to scroll down with the text and be half way down the div....here is the code...
<div id="barino_info">
<div id="barino_header"></div>
<div class="info_body">
<div id="barino_left_body">
</div>
<div class="right_body">
<div class="right_body_outer">
<ul class="play_navigation">
<li class="active">The Story</li>
<li>The Video</li>
<li><a onclick="show_gallery('barino');" href="#" class="navlinks barino_gallery_bottom" ref="bottom_barino_gallery">The Gallery</a></li>
<li>The Equipment</li>
</ul>
<div class="right_body_inner tab_content">
Barino's Market is an ......
The image is a background url on #barino_left_body
Any ideas on a strategy on how to achieve this..i was thinking of maybe jquery offset or something like that
I am using this plugin to do the expanding...here is my jquery to do it
$('.right_body_inner, .doughboy_right_body_inner, .haw_right_body_inner, .river_right_body_inner, .vib_right_body_inner, .barino_equipment, .dough_equipment, .river_equipment, .haw_equipment, .vib_equipment').expander({
slicePoint: 355, // default is 100
expandEffect: 'fadeIn',
expandSpeed: '8', // speed in milliseconds of the animation effect for expanding the text
userCollapseText: '(less..)' // default is '[collapse expanded text]'
});
After the "read more" is clicked, you can calculate the height of the surrounding <div> and add (or animate) the top margin of #barino_left_body to half the surrounding <div> height plus half the height of #barino_left_body.
$('.read-more').click(function(event){
var parentHeight = $(this).parents('.info_body').height();
var imageHeight = $('#barino_left_body').height();
var centerImage = (parentHeight-imageHeight)/2;
$('#barino_left_body').animate({"marginTop": centerImage}, 250);
event.preventDefault();
});
P.S. I'm curious as to why you made it a background image.
Try this code on for size, I might get some references wrong with those parent/prev, if you can't figure it out, post the errors.
JS
$('.right_body_inner, .doughboy_right_body_inner, .haw_right_body_inner, .river_right_body_inner, .vib_right_body_inner, .barino_equipment, .dough_equipment, .river_equipment, .haw_equipment, .vib_equipment').expander({
slicePoint: 355, // default is 100
expandEffect: 'fadeIn',
expandSpeed: '8', // speed in milliseconds of the animation effect for expanding the text
userCollapseText: '(less..)', // default is '[collapse expanded text]',
afterExpand: function($thisElement) {
$thisElement.parent().prev().height($thisElement.height())
},
onCollapse: function($thisElement, byUser) {
$thisElement.parent().prev().css('height','');
}
});
I add 2 callbacks basically.
The first one afterExpand sets the height to the height of the expanded div. The CSS you have already sets the position of the image properly.
The second one onCollapse sets removes my previous definition for height, returning to the div to it's CSS definition and bringing the layout back to it's original design.
Used .css() in the second function just so you can see the different approaches to setting height, you can decide between the two.
I'm not sure if you can move a background image with jQuery. I think you cannot. However you do can move a normal image, so that's what I would do.
I would select the image through jquery and then use animate() to set the new position of it. css() would work too. The top will be ((the total size of the div) - (the size of the image))/2
var h1 = $("#barino_left_body").height();
var h2 = $("#myimage").height();
var position = (h1-h2)/2;
$("#myimage").animate({"top":position},250);
You do this when expanding the text.
This should do the trick. But if you really need to use the image as background I don't know your answer. :(
I hope it helped.

Categories

Resources