Ok. I want to apologize first because I don not even know how to title this question properly.
I will try to explain:
I have a working code for this that kind-of-works, but it is a bit messy, so I am trying to divide it into small functions.
The whole process is a simulated scrollbar:
-div id="scrollbar" represents the scrollbar track.
-div id="scrollbar"'s only child represents the scrollbar thumb.
Now. Whenever an "onmousedown" event takes place in document.documentElement, function checkAndPerform(e) gets executed.
checkAndPerform(e) gets borders of scrollbar and calls the following functions:
-checkClick(e): It checks if click has occurred inside scrollbar and returs true or false.
-perform(): If result of checkClick is true, it moves thumb to click position. Also, it scrolls down the section where the scrollbar operates (section id="ejercicios"). Finally, it adds a "mousemove" EventListener to document.documentElement, with function followMe(e) attached to it.
This followMe function calls a function that limits scroll to trackbar borders. After that it perform translation of div and scrolling of section while "mousemove" is active, and finally calls a function release(), that adds a "mouseup" EventListener to remove the followMe function after mouse button gets released.
The idea for the code was got here:
How can I retrieve all mouse coordinates between mousedown to mouseup event, where you can see in accepted answer that function called "trackPoints" gets called troubleless (as it is in my previous code).
So, here it is troubling javascript code:
function getHeight(object) {
var height = object.getBoundingClientRect().bottom - object.getBoundingClientRect().top;
return height;
}
function checkClick(e) {
console.log("x:" + e.pageX, "y:" + e.pageY);
if (e.pageX > sBLeft - 5 && e.pageX < sBRight + 5 && e.pageY < sBBottom && e.pageY > sBTop) {
adentroBar = true;
console.log("meas adentro");
} else {
adentroBar = false;
console.log("meas afuera");
}
return adentroBar;
}
function scrollLimited(e) {
if (e.pageY < sBTop) {
translateY = 0;
} else if (e.pageY > sBBottom) {
translateY = getHeight(scrollBar) - getHeight(thumb);
} else {
translateY = e.pageY - sBTop - .5 * getHeight(thumb);
}
}
function followMe(e) {
scrollLimited;
thumb.style.transform = "translate3d(0," + translateY + "px,0)";
document.getElementById('ejercicios').scrollTop = translateY * ratio;
document.documentElement.addEventListener("mouseup", release, false);
}
function perform() {
if (adentroBar === true) {
translateY = e.pageY - sBTop - getHeight(thumb) / 2;
}
thumb.style.transform = "translate3d(0," + translateY + "px,0)";
document.getElementById('ejercicios').scrollTop = translateY * ratio;
document.documentElement.addEventListener("mousemove", followMe, false);
}
function release() {
document.documentElement.removeEventListener("mousemove", followMe, false);
}
function checkAndPerform(e) {
var translateY, adentroBar, scrollBar = document.getElementById('scrollbar'),
thumb = scrollBar.getElementsByTagName("div")[0],
sBLeft = scrollBar.getBoundingClientRect().left,
sBRight = scrollBar.getBoundingClientRect().right,
sBTop = scrollBar.getBoundingClientRect().top,
sBBottom = scrollBar.getBoundingClientRect().bottom,
preg = document.getElementById('preguntas'),
ratio = preg.offsetHeight / (getHeight(scrollBar) - getHeight(thumb));
if (e.which === 1) {
checkClick;
perform;
}
}
document.documentElement.addEventListener("mousedown", checkAndPerform, false);
Also, here it is a jFiddle for it: https://jsfiddle.net/pa0exs4q/
I may provide the working code in case you find it interesting, but as i have said, it is really messy and porly written.
Problem is second function in flow (checkClick), is not even being called.
I have tried to call it as (checkClick(e)), in that case it runs, but fails to recognize variables defined above in checkAndPerform.
In my working code, everything was an only function, so i think it may be a scope problem, but I am open for any ideas.
If you have a function named myFunction, you can pass it around simply as myFunction, but to actually call it you have to write it as myFunction(). So this:
function checkAndPerform(e){
var translateY, adentroBar, scrollBar=document.getElementById('scrollbar'), thumb=scrollBar.getElementsByTagName("div")[0], sBLeft=scrollBar.getBoundingClientRect().left, sBRight=scrollBar.getBoundingClientRect().right, sBTop=scrollBar.getBoundingClientRect().top, sBBottom=scrollBar.getBoundingClientRect().bottom, preg=document.getElementById('preguntas'), ratio=preg.offsetHeight/(getHeight(scrollBar)-getHeight(thumb));
if (e.which===1){
checkClick;
perform;
}
}
Should become this:
function checkAndPerform(e){
var translateY, adentroBar, scrollBar=document.getElementById('scrollbar'), thumb=scrollBar.getElementsByTagName("div")[0], sBLeft=scrollBar.getBoundingClientRect().left, sBRight=scrollBar.getBoundingClientRect().right, sBTop=scrollBar.getBoundingClientRect().top, sBBottom=scrollBar.getBoundingClientRect().bottom, preg=document.getElementById('preguntas'), ratio=preg.offsetHeight/(getHeight(scrollBar)-getHeight(thumb));
if (e.which===1){
checkClick(e);
perform(e); // make sure to pass e and expect it in perform, otherwise it won't have access to it
}
}
Without seeing your corresponding markup, this seems to me the most likely and glaring issue in your code.
Related
I need a script which toggle a class when another class or section is visible in the viewport (during scrolling).
Here I have an script which works for precised distance from top, but can somebody help me to modify it for my needs?
$(window).scroll(function(){
if ($(this).scrollTop() > 50) {
$('#viewport').addClass('turn_on');
} else {
$('#viewport').removeClass('turn_on');
}
});
A couple of things. First the scroll event (as well as the resize event) fire multiple times. Traditionally, developers have used something called debouncing to limit the number of times a function fires. I've never got it to work correctly, so instead I check if a condition is met before continuing. You are basically doing this already.
var bool = false
$(window).on('scroll', function(){
if(!bool){
bool = true;
//fire the function and then turn bool back to false.
};
});
The next thing you need is to identify the element to add the class to. Let's say it has an id of foo.
var yOffset = $('#foo').offset().top;
From here, you'll need to compare the current vertical scroll position with that of the yOffset. You may also need to add the height of the element for when it scrolls out of frame.
var elHeight = $('#foo').height();
The element should be completely in frame with the $(window).scrollTop() equals the yOffset and out of frame when the $(window).scrollTop() is greater than yOffset + elHeight.
This is all assuming the element isn't in the frame to begin with. If it is, it will be trickier but it's a start.
Working fiddle
Try to add function that detect if element passed in argument is visible :
function isVisisble(elem){
return $(elem).offset().top - $(window).scrollTop() < $(elem).height() ;
}
$(window).scroll(function(){
if (isVisisble( $('your_element') ))
$('#viewport').addClass('turn_on');
} else {
$('#viewport').removeClass('turn_on');
}
});
Hope this helps.
Thx everyone for help.
Here I found the solution: LINK
And here is the modified script:
$(document).ready(function () {
var windowHeight = $(window).height(),
gridTop = windowHeight * 0.1,
gridBottom = windowHeight * 1;
$(window).on('scroll', function () {
$('.inner').each(function () {
var thisTop = $(this).offset().top - $(window).scrollTop();
if (thisTop > gridTop && (thisTop + $(this).height()) < gridBottom) {
$(this).addClass('on');
}
});
});
});
I'm working on building a infinite scroll feature in an Angular app. Building 1 step at a time, currently I'm at the part where I've attached a scroll eventListener to the #tags-col or tagsCol.
plnkr: http://plnkr.co/edit/7OnKAGvVNXWLwN5fZqJu?p=preview
I have code that is suppose to return the top y value of tagsCol, however the number never updates with the logs as you scroll the element up, it stays stuck at 118.8125.
How would you get the moving y value of the tagsCol? Or better yet, detect when the bottom / last element inside of the tagsCol panel is visible?
function getOffset(el) {
el = el.getBoundingClientRect();
return {
top: el.top + window.scrollY
}
}
var scrollingElement = function(event) {
console.log(tagsCol);
var yPos = getOffset(tagsCol).top
console.log(yPos);
};
document.getElementById('tags-col').addEventListener('scroll', scrollingElement);
Maybe this solution might work for you.
var el = document.getElementById("tags-col");
var offsetTop = el.offsetTop;
function callback() {
if (offsetTop < window.pageYOffset) {
// do something
} else {
// do something else
}
}
if (window.addEventListener) {
window.addEventListener("scroll", callback, false);
} else {
window.attachEvent("onscroll", callback);
}
Does that make sense?
So I am writing a sort of drawing script, and it works fine right now (although the code still needs to be cleaned up and there needs to be more features), but when painting too much, mousemove lags incredibly. Here is the main Javascript:
$('#canvas').on('mousedown', function(){
going = !going;
$(this).on('mousemove', function(e){
if(cursor == 'paint' && going == true){
$('.fall').each(function(){
if ($(this).css("opacity") == 0){
$(this).remove();
};
});
var ps = $('#canvas').offset().top;
var t = (e.pageY - ps - $('.fall').height()).toString() + 'px';
var l = (e.pageX - $('.fall').width()).toString() + 'px';
$('.fall').css("margin_left",l);
$('.fall').css("margin_top",t);
var doit = '<div class="fall" style="position:absolute;margin-left:' + l + ';margin-top:' + t + ';background-color:'+ color +';box-shadow: 0px 0px 5px ' + color + ';"></div>'
$('#canvas').prepend(doit);
}
else if(cursor == 'erase'){
$('.fall').mouseenter(function(){
$(this).fadeOut('fast',function(){
$(this).remove()
});
});
};
});
Essentially, when you click in the section for drawing, if the paint button is clicked, you can draw: jsfiddle.
My issue:
If you draw too much, especially with starting and stopping, it does not append enough on the mousemove do to (I assume) the DOM being overwhelmed.
Question:
What would be an efficient way to add many many divs to the DOM without creating a lag? Is this possible?
Note:
this is a personal project and I am not interested in using previously created drawing APIs
There's a lot you can do to improve performance.
The code below is a heavy refactoring of the code in the question. At first glance it might appear to be less efficient as it has about double the number of lines of the original. However, line count isn't the issue here. Two basic principles apply:
do as little DOM interaction as possible in the mousemove handler, and as much as possible on mousedown.
include a "divider circuit" to limit the number of times the mousemove handler is called. This is achieved by detaching the mousemove event handler on every call and reattaching after a short delay, conditional on the mouse still being down.
Also see comments in the code.
jQuery(function($) {
...
var $canvas = $("#canvas");
var data = {
name: 'fall'//a unique string for namespacing the muousemove event.
};
$canvas.on('mousedown', function() {
going = !going;
data.$fall = $('.fall');//this collection is created once per mousedown then managed inside mm to avoid unnecessary DOM interaction
data.mousedown = true;
data.colorCSS = {
'background-color': color,
'box-shadow': '0px 0px 5px ' + color
};
data.fallWidth = data.$fall.width();
data.fallHeight = data.$fall.height();
attachMouseMoveHandler();
}).on('mouseup', function() {
data.mousedown = false;
}).trigger('mouseup');
function attachMouseMoveHandler() {
if(data.mousedown);
$canvas.on('mousemove.' + data.name, mm);//the event is namespaced so its handler can be removed without affecting other canvas functionality
}
//The mousemove handler
function mm(e) {
if(going && cursor == 'paint') {
data.$fall.each(function() {
data.$fall = data.$fall.not(this);//manage data.$fall rather than re-form at every call of mm()
var $this = $(this);
if ($this.css("opacity") == 0) {
$this.remove();
};
});
data.$fall = data.$fall.add($('<div class="fall" />').css(data.colorCSS).prependTo($canvas)).css({
'margin-left': (e.pageX - data.fallWidth) + 'px',
'margin-top': (e.pageY - $canvas.offset().top - data.fallHeight) + 'px'
});
}
else if(cursor == 'erase') {
data.$fall.mouseenter(function() {
data.$fall = data.$fall.not(this);//manage data.$fall rather than re-form at every call of mm()
var $this = $(this).fadeOut('fast', function() {
$this.remove();
});
});
};
$canvas.off('mousemove.' + data.name);
setTimeout(attachMouseMoveHandler, 50);//adjust delay up/down to optimise performance
}
});
Tested only for syntax
I had to make a number of assumptions, chiefly concerning what becomes fixed data on mousedown. These assumptions may be incorrect, so you will most probably still have some work to do, but as long as you work inside the overall framework above, there's a good chance that your performance issues will disappear.
I have the following JS function, which I am using to detect when the cursor is hovering over a particular image on an HTML5 canvas:
function cursorOverAssetsBox(mouseX, mouseY){
if((mouseX >= assetsDescriptionBox.img_x && mouseX <= assetsDescriptionBox.img_x + assetsDescriptionBox.img_width) &&
(mouseY <= assetsDescriptionBox.img_y && mouseY >= assetsDescriptionBox.img_y - assetsDescriptionBox.img_height))
document.getElementById('tipsParagraph').innerHTML = tips[34];
console.log(tips[34]);
}
The function works correctly, but for some reason, the line
document.getElementById('tipsParagraph').innerHTML = tips[34];
doesn't appear to be firing...
'tipsParagraph' is the ID I have given to an HTML <p></p> tag in the <body></body> of my HTML. 'tips' is an array, each element of which contains some text.
I want to display the text stored in position 34 of that array whenever the cursor is in the location specified by my if statement. I know that the function works, because the line console.log(tips[34]); is displaying the contents of that element of the array in the console whenever the cursor is in that location. But for some reason, the text in the paragraph is not updated to display the contents of that array element.
Can anyone figure out why this is?
I am calling the function from within the mousemove function of a local copy of the KineticJS library. I'm using a local copy of the library, as there are a couple of slight adjustments I wanted to make to its functionality, such as this one. The 'mousemove' function currently looks like this:
_mousemove: function(evt) {
this._setUserPosition(evt);
var dd = Kinetic.DD;
var obj = this.getIntersection(this.getUserPosition());
getMousePosition(evt);
document.getElementById('mouseLocation').innerHTML = "mouseX = " + evt.clientX + ". mouseY = " + evt.clientY;
/*Add an if statement, so that cursorOverAssetsBox function is only called when the cursor's y value is greater than
400. This will improve performance, as the code will only check if it needs to call the function when the cursor is
below that line. */
if(mouseY >= 400){
cursorOverAssetsBox(mouseX, mouseY);
}
/*Write an if statement that says, "if 'draggingImage' variable is set to true, check the x & y of that image to see if
it's over its description box- if it is, do something, if not, don't", if 'draggingImage' variable is not set
to true, don't check.*/
//cursorOverAssetsBox(mouseX, mouseY);
if(obj) {
var shape = obj.shape;
if(shape) {
if((!dd || !dd.moving) && obj.pixel[3] === 255 && (!this.targetShape || this.targetShape._id !== shape._id)) {
if(this.targetShape) {
this.targetShape._handleEvent('mouseout', evt, shape);
this.targetShape._handleEvent('mouseleave', evt, shape);
}
shape._handleEvent('mouseover', evt, this.targetShape);
shape._handleEvent('mouseenter', evt, this.targetShape);
this.targetShape = shape;
}
else {
shape._handleEvent('mousemove', evt);
}
}
}
/*
* if no shape was detected, clear target shape and try
* to run mouseout from previous target shape
*/
else if(this.targetShape && (!dd || !dd.moving)) {
this.targetShape._handleEvent('mouseout', evt);
this.targetShape._handleEvent('mouseleave', evt);
this.targetShape = null;
}
// start drag and drop
if(dd) {
dd._startDrag(evt);
}
}
I found this script by theZillion (http://thezillion.wordpress.com/2012/08/29/javascript-draggable-no-jquery/) that makes a div draggable. I'm trying to use this script to move a div by class name. And not by ID.
I have an event handler that works, but not when I'm adding the script... The console shows no errors either. Any ideas about how to make this work?
This is the code I have:
function wrappmover(){
var moveEvent = "dice-window-wrapper";
var addClassArr= document.getElementsByClassName(moveEvent);
for(var i=0; i<addClassArr.length; i++){
var addClass = addClassArr[i];
addClass.addEventListener("click", movewrapp, true);
}
function movewrapp() {
var classToMove = "dice-window-wrapper";
var elems = document.getElementsByClassName(classToMove);
var tzdragg = function(){
return {
startMoving : function(evt){
evt = evt || window.event;
var posX = evt.clientX,
posY = evt.clientY,
a = document.getElementsByClassName(classToMove),
divTop = a.style.top,
divLeft = a.style.left;
divTop = divTop.replace('px','');
divLeft = divLeft.replace('px','');
var diffX = posX - divLeft,
diffY = posY - divTop;
document.onmousemove = function(evt){
evt = evt || window.event;
var posX = evt.clientX,
posY = evt.clientY,
aX = posX - diffX,
aY = posY - diffY;
tzdragg.move('elem',aX,aY);
}
},
stopMoving : function(){
document.onmousemove = function(){}
},
move : function(divid,xpos,ypos){
var a = document.getElementById(divid);
document.getElementById(divid).style.left = xpos + 'px';
document.getElementById(divid).style.top = ypos + 'px';
}
}
}();
Okay, so you want to have draggable elements on your page?
Take a look at the following code (and here's a working example). I hope you will find it self-explanatory, but just in case there are also comments:
// Wrap the module in a self-executing anonymous function
// to avoid leaking variables into global scope:
(function (document) {
// Enable ECMAScript 5 strict mode within this function:
'use strict';
// Obtain a node list of all elements that have class="draggable":
var draggable = document.getElementsByClassName('draggable'),
draggableCount = draggable.length, // cache the length
i; // iterator placeholder
// This function initializes the drag of an element where an
// event ("mousedown") has occurred:
function startDrag(evt) {
// The element's position is based on its top left corner,
// but the mouse coordinates are inside of it, so we need
// to calculate the positioning difference:
var diffX = evt.clientX - this.offsetLeft,
diffY = evt.clientY - this.offsetTop,
that = this; // "this" refers to the current element,
// let's keep it in cache for later use.
// moveAlong places the current element (referenced by "that")
// according to the current cursor position:
function moveAlong(evt) {
that.style.left = (evt.clientX - diffX) + 'px';
that.style.top = (evt.clientY - diffY) + 'px';
}
// stopDrag removes event listeners from the element,
// thus stopping the drag:
function stopDrag() {
document.removeEventListener('mousemove', moveAlong);
document.removeEventListener('mouseup', stopDrag);
}
document.addEventListener('mouseup', stopDrag);
document.addEventListener('mousemove', moveAlong);
}
// Now that all the variables and functions are created,
// we can go on and make the elements draggable by assigning
// a "startDrag" function to a "mousedown" event that occurs
// on those elements:
for (i = 0; i < draggableCount; i += 1) {
draggable[i].addEventListener('mousedown', startDrag);
}
}(document));
Load or wrap it in <script></script> tags as close as possible to </body> so that it doesn't block the browser from fetching other resources.
Actually, if you remove the comments, it's a very small function. Much smaller and more efficient than the one from the website you've provided.
A possible improvement
Consider replacing the anonymous wrapper with something like makeDraggable(selector); where selector is a CSS selector, so you could do crazy stuff like:
makeDraggable('#dragMe, #dragMeToo, .draggable, li:nth-child(2n+1)');
It can be achieved by using document.querySelectorAll that is able to perform complex CSS queries instead of a simple class name lookup by document.getElementsByClassName.
Things to watch out for
If the page has any scrolling - the drag will look broken; consider adjusting the positioning of the dragged element by scrollX and scrollY
This will obviously not work in Internet Explorer (figure it out yourself).
There might be memory leaks (needs profiling and testing).
EDIT: A solution for adding new draggable elements
So you want to be able to add more draggable elements? There are several approaches to tackle this. For example you could write a makeDraggable(element); function and call it on the element you're adding to the DOM. It will work of course, but let's have a look at something different, shall we?
Instead of querying the DOM in search of draggable elements and assigning them event listeners, why don't we assign just one for the "mousedown" event on document body.
When triggered, the event object will contain a reference to the target element which is the object the event has been dispatched on (the element you mousedown-ed). The relevant part of the code will now resemble this:
// Performs a check if the current element is draggable and if yes,
// then the dragging is initiated:
function startDragIfDraggable(evt) {
// Check if the target element (referenced by evt.target) contains a
// class named "draggable" (http://stackoverflow.com/questions/5898656/):
if (evt.target.classList.contains('draggable')) {
// Invoke startDrag by passing it the target element as "this":
startDrag.call(evt.target, evt);
}
}
// Listen for any "mousedown" event on the document.body and attempt dragging
// the target element (the one where "mousedown" occurred) if it's draggable:
document.body.addEventListener('mousedown', startDragIfDraggable);
And here's a working example of the above code. As a bonus it features a simulation of adding new draggable elements to the DOM.
In addition to being able to drag dynamically-added draggable elements, this approach will also save us some memory because we can now avoid assigning a bunch event listeners to a bunch of elements. However, if the application you're developing is very click-intensive (e.g. a game) then you might waste a little bit of CPU because of checks on every click.