I have a small draggable division (black) and many nodes with different IDs beside it
I need a hovering effect when I drag the black element on the nodes. What I am doing in allowDrop function is:
var dragObj;
function drag(ev){
dragObj = ev;
}
function allowDrop(ev){
ev.preventDefault();
var Dragged = dragObj;
var Hovered = ev;
var leftIndent = Dragged.layerX;
var hoveredID = Hovered.target.id.toString().split('_');
var nodesOnLeft = Math.floor(leftIndent/12);
var startingNode = hoveredID[0]-nodesOnLeft;
for (i=1;i<=Math.floor(draggableElementLength/12);i++){
var toApplyCssID = startingNode.toString() + '_1';
var toApplyCss = document.getElementById(toApplyCssID);
$('#'+toApplyCssID).css('background-color','lightgreen');
}
}
basically I am using the layerX property to find out the distance between my mouse pointer and draggable division's border and adjusting that to calculate number of nodes where I have to apply new CSS and applying that by finding the ID of node.
This is working but its making the process very slow as it involves many calculations and its not the hover effect as its not going away when I am removing the draggable division.
Is there any other way to achieve this or do I need to make code changes in existing code.
thanks
Without the HTML and the rest of the script, I can only point you in the direction you should be taking in this kind of scenario:
Don't constantly repeat calculations (that do not change) in a loop, store them outside the function:
Use document.getElementById(toApplyCssID) for each node and store the
elements in an array
Get the distance from the mouse position to the required edge or
edges of the div (leftIndent) on initial drag/mousedown and store
that in a variable
Store Math.floor(draggableElementLength/12) in another variable (you
haven't shown where this originates, but it doesn't seem to change in
the function...)
Apply the CSS to the relavent elements (from the array of elements)
using the other stored values to select them
Remove the CSS on those that had it applied earlier
This may not be the ultimate solution, but these are some of the things you can look at to speed up what you (may) have currently.
Related
I'm using Web Animations API to animate some img DOM elements to make an image slider for a (kind of) sing along game. The image slides represent bars in sheet music, and each slide contains two bars. The slides currently animate starting at slide0 and ending at slide55 (or looping if Infinite is specified in the timing object), and works fine with its relative song. As a part of the game the users should be able to choose a portion of the song to play on loop. For example, if they wanted to keep looping slide16-slide32, then the animation should only target these slides.
I'm wondering if there is a way of manipulating the animation objects to achieve a functionality similar to this. I am reading a lot of great things about how WAAPI allows you to dynamically manipulate the running animation once it has started, but I can't find a way to manipulate which animation objects are actually animated. These animation objects have id's, but I can't find any examples of how they could be used.
The only solution I can think of is by limiting which elements to animate just before calling Element.animate() on each, something like:
var firstBar = 4;
var lastBar = 12;
async function init() {
var elements = document.querySelectorAll(`.slide`);
elements = Array.prototype.slice.call(elements);
var loopElements = await loopBars(firstBar, lastBar, elements)
//map the elements and call Element.animate() method for each
loopElements.map((element, i) => {
imgAnimations[i] = element.animate(keysframes, timing)
})
};
function loopBars(firstBar, lastBar, elementArray) {
var elements = []
for(firstBar; firstBar <= lastBar; firstBar++) {
var slide = elementArray[firstBar];
var pushToArray = elements.push(slide)
}
return elements
}
However this would also involve changing the CSS positions of the slides, and just more or less defeats the purpose of using WAAPI for its capabilities of manipulating the animation objects after they've been created. I could also apply a variation of this function when the img tags are being created and added to DOM, but this would require removing and re-adding the DOM elements each time a user wants to loop different sections of the song.
I have made a JS fiddle to show how the elements are added to dom and animated with WAAPI, it includes all the functions I have discussed. The animation just loops the same 5 images. Any suggestions on my potential next move would help, thanks.
https://jsfiddle.net/j84ksdLp/122/
I am just trying to get the mouse hover div's position at the right according to the space around. Somehow I am able to do this in first two columns but not for other columns. May be my calculations while writing the condition state are wrong.
Can anyone please help?
JS Fiddle URL:
http://jsfiddle.net/mufeedahmad/2u1zr11f/7/
JS Code:
$('.thumb-over-team li').find('.cover').css({opacity:0});
$('.thumb-over-team li').on('mouseenter', function(){
var $this = $(this),
thisoffset = $this.position().left,
openDivId = $(this).find('.cover'),
thumbContainer = '.thumb-over-team',
speedanim = 200;
if(thisoffset + openDivId.outerWidth() >= $(thumbContainer).outerWidth()){
//thisoffset = $(thumbContainer).outerWidth() - openDivId.outerWidth() - 212;
thisoffset = thisoffset - openDivId.outerWidth()+10;
openDivId.stop().css({'z-index':'9999'}).animate({'opacity':'1', 'left':-thisoffset}, 200);
}else{
openDivId.stop().css({'z-index':'9999'}).animate({'opacity':'1', 'left':'212px'}, 200);
}
}).on('mouseleave', function(){
$(this).find('.cover').stop().css({'z-index':'-1'}).animate({'opacity':'0', 'left':'200px'}, 200);
});
$('.close-parent').on('click', function(){
$(this).parents('.cover').stop().css({'z-index':'-1'}).animate({'opacity':'0', 'left':'200px'}, 200);
});;
In your first conditional, try to calculate the position of the offset as:
thisoffset = ($(thumbContainer).outerWidth() - openDivId.outerWidth() - thisoffset);
That way, you're adjusting the appearing square (.cover) when it doesn't fit inside the container, to be as close possible to its rightmost edge: (maximum width - appearing square width - current li position)
Calculated this way, you can animate it with the new offset in positive:
openDivId.stop().css({'z-index':'9999'}).animate({'opacity':'1', 'left':thisoffset}, 200);
See it working here.
For elements that "almost" fit, the current system isn't completely precise because of what I already pointed out in my previous comment: the appearing square, even if it were at 0 opacity, would still be inside the containing element (($(thumbContainer)) or .thumb-over-team) and it would add its width to the total width of the container.
So your conditional may think that there's enough available space in the container to make the expandable element fit, but that would go out of the screen. As an example, notice that there's a horizontal scrollbar from the very beginning, caused by this effect, where your containing .thumb-over-team element doesn't fit in the screen.
But I would say that more precision in this point would require a fresh new approach to your system where the appearing .cover elements were out of the containing ul .thumb-over-team
Fresh take on the problem, essentially based on the main issue: the expandable text block (.cover) used to add its width to the container (.thumb-over-team). This altered the calculations on available container space, and made the text containers go off screen.
The solution is to make sure the expandable .cover elements aren't contained inside the .thumb-over-team element, so they won't impact the calculations on available width.
Here is a JSFiddle containing this new approach: link.
Explanation of how it works:
The idea was to create a separate element called .cover-container and let's put all the expandable .cover elements in there.
We want to associate every image in the li elements in .thumb-over-team with their appropriate .cover (so the first image triggers the first .cover to show, the second image would show the second cover, and so on.) We achieve is by finding out the index of the element that triggered the event:
thisLiIndex = $this.index() + 1
And then selecting the cover in the matching position:
openDivId = $('.cover-container .cover:nth-child(' + thisLiIndex + ')')
The expandable covers shouldn't interfere with the mouseenter or mouseleave events of .thumb-over-team, so we make it to ignore mouse events via CSS:
.cover-container{pointer-events:none;}
Changing from one image to another would automatically trigger new events, so the expanding covers stay visible when the mouse stays on the images, but close automatically when the mouse exits them.
Since the covers are now outside of $(thumbContainer), openDivID.outerWidth() does not alter $(thumbContainer).outerWidth(), and we can use that safely in our positioning.
If I understand the placement that you want, for covers that fit, the position is the current offset (position of the li element that triggered the event) plus the width of the image and some subtle margin
imageWidth + rightSeparation + thisoffset
And for covers that won't fit inside of the screen, we keep them just inside of the screen
thisoffset = $(thumbContainer).outerWidth() - openDivId.outerWidth();
At the moment I have some jQuery code telling a div called small-circle to follow my cursor around. I learnt how to achieve this from a post on stack overflow.
I modified the script very slightly to work with my own requirements. However there's is an issue when I add another big-circle to my container class. What seems to be happening is that the id of small-circle2 is not allowed to move around inside of the big-circle2 on the x axis. I'm thinking it has something to do with
var mouseXval = 0, mouseYval = 0, limitX = 120-30, limitY= 120-30;
limitX and limitY values might need to change because I am adding another div called big-circle2 to the mix. So I tried this approach by adding 60 to limitX = 120-60, limitY= 120-60; but the only that changed was the placement of the circle. small-circle2 was not following my cursor around on the x axis. Still only the y axis.
I'm thinking that the possible solution could be in the math that is going on inside of the limitX and limitY axises. Another problem could be trying to add multiple circles inside of the one container.
Here's the fiddle.
Thank you!
Thax to #blex I was able to see where I was off. As it turns out there was nothing wrong with the math in limitX and limitY and it was not a container issue. What was I thinking!
The problem was here inside of the pageOffset variable
// issue with selecting both classes
var pageOffset = $(".big-circle, .big-circle2").offset();
The issue here was in selecting both ".big-circle, .big-circle2" to trigger the offset()method. Instead of selecting each class and then applying offset() I should of used the this keyword to trigger any of the two circles I hovered on.
// use `this` to trigger the elements I hover on
var pageOffset = $(this).offset();
When the user moves the mouse over ".big-circle, .big-circle2" jQuery fires a callback function which is passed onto the mousemove event. As soon as our callback is fired jQuery sets the current scope to the DOM elements that initiated the event which in this case are .big-circle and .big-circle2. The cool part about all of this is that jQuery gives us access to the elements that initiated the mousemove event with the this keyword.
So we set var offsetPage = $(this).offset(); and our big-circle2 now has little circle that follows are cursor around.
// jQuery sets the scope of callback to `big-circle, big-circle2`
$(".big-circle, .big-circle2").mousemove(function(event){
// `this` accesses our elements
var pageOffset = $(this).offset();
});
Updated fiddle
it's my first time to use this plugin.
I want to add a stacking of elements whenever it's dragged. I use z-index and position relative.
onstart: function (event) {
//get max z-index on page
var maxZ = Math.max.apply(null,
$.map($('body > *'), function(e,n) {
if ($(e).css('position') != 'static')
return parseInt($(e).css('z-index')) || 1;
}));
event.target.style.background = 'red';
event.target.style.zIndex = maxZ + 1;
event.target.style.position = 'relative';
},
Is there more efficient way to do this?
original drag and drop demo
my edited drag and drop demo with stacking
Your solution works well if the elements are not also used as dropzones. When dropzones overlap, interact.js chooses the element that is deepest in the DOM and so should appear above the other dropzones ulness some CSS properties change the drawing order (eg. position, transform, z-index, etc.) If overlapping dropzones are re-ordered with z-index then it might happen that the drop targets look incorrect.
If the elements that you're targeting are all siblings and all have absolute or fixed position (before any drag happens), then appending one element to it's parent should bring it in front of the others without using z-index so drop checks should work fine.
onstart: function (event) {
var target = event.target;
// Bring element in front of its siblings
target.parentNode.appendChild(target);
...
}
How can one tell, using Javascript (jQuery will work), if a particular X-Y coordinate on an HTML page is vertically between two lines of text? The lines may be in the middle of a long paragraph, inside a lengthy line-item tag, in a span, between two tags, etc. I have no way of controlling the HTML or the X-Y point, but I need to know if the X-Y point is in the middle of a line of text, or if it is in-between two lines of text; and it needs to be pretty efficient.
Please ask any questions you may have if I have not been clear enough.
Many thanks.
You can call .getBoundingClientRect() on a text range. You will need to write separate code for IE vs Non-IE browsers to get the text ranges.
This should be relatively easy in IE, thanks to textRange.moveToPoint(x, y). For other browsers you'll have to do something like do a binary search on the elements in the DOM, calling .getBoundingClientRect() on the elements, until you find the element that contains your text. Then create a range that contains the text of that element and do a binary search on the range to find whether your point overlaps any text.
All of this will be greatly complicated if you have absolutely positioned elements with text overlapping other elements.
Having dealt with text ranges, I don't think you can technically put anything "between" two lines of text on the same HTML node. Even if you use line height, every pixel belongs to one of the lines (even though it's visually space between them).
I'll throw out a few options which might help.
The simplest answer is probably just to use the line height:
get dom element that was clicked (event.relatedTarget in jQuery?)
determine its offset relative to the page (i.e. where the top of that element is)
determine the point that was clicked (x,y coords of the mouse event)
compare the two using the line-height of text in the row
This would look something like this:
function getLines(topOfElement, clickPoint, lineHeight) {
return Math.floor( (clickPoint - topOfElement)/lineHeight );
}
var topOfElement = $(element).offset().top; //must be position: relative|absolute
var clickedPoint = event.clientY; //might be pageY?
var lineHeight = parseFloat($(element).css('line-height')); //probably need to set this in px using css or it might be null
var textHeight = parseInt($(element).css('font-size')); //probably need to set this in px using css or it might be null
var prevLineNumber = getLines(topOfElement, clickedPoint, lineHeight);
// the previous line ends (in theory) at the bottom of the text (textHeight)
// you might need to adjust this definition to your needs
var prevLineBottom = prevLineNumber*lineHeight+topOfElement+textHeight;
// the next line begins (in theory) at the top of its line
// you might need to adjust this definition to your needs
var nextLineTop = (prevLineNumber+1)+lineHeight;
if( clickedPoint >= nextLineTop ) {
alert('clicked on row '+(prevLineNumber+1));
}
else if( clickedPoint <= prevLineBottom ) {
alert('clicked on row '+prevLineNumber);
}
else {
alert('clicked between rows '+prevLineNumber+' and '+(prevLineNumber+1));
}
If you want to see if the click happened between two html nodes, you can do that with Rangy, as well as some fancy selection and range calculations.
You could use it for things like determining the exact length of the text before and after the seletion. This is only useful if you want to see where in the text they clicked.
function getTextAtClick() {
var result = {nodeClicked: null, textBefore: '', textAfter: '', valid: false};
//get a selection object (even though the selection is technically zero length)
var sel = rangy.getSelection();
//you would probably want to discard any selection not zero length (i.e actual selection of text instead of a click)
// if not, you'd need to decide what it means to select across multiple dom nodes :(
if( sel.toString().length > 0 ) { return result; }
// get the point where the click occurred
var range = sel.getRangeAt(0);
result.valid = true;
// determine text in our dom element up to the click point
var before = rangy.createRange();
before.setStart(range.startContainer, 0);
before.setEnd(range.startContainer, range.startOffset);
result.textBefore = before.toString();
// determine text in our dom element after the click point
var after = rangy.createRange();
after.setStart(range.startContainer, range.startOffset+1);
after.setEndAfter(range.startContainer);
result.textAfter = after.toString();
return result;
}