Using Javascript getBoundingClientRect to Snap Items to Grid - javascript

EDIT: I've simplified the code (below and in fiddle) down to the major problem needed to be solved in hope of creating more readability.
I've implemented Bader's solution for correctly using getBoundingClientRect value and using document.querySelector for getting both the class name and the html tag needed for the function. I'd now like to move on to the last five lines of the code beginning with var = style.
I've now corrected the math for the final two variables.
→ I'm trying to achieve creating a snapping function for use alongside Plumber, a baseline-grid Sass plugin.
Basically, I have a vertically centered flex item that needs to -- instead of being perfectly centered -- snap in an upward direction to the closest grid line. This will allow me to have a consistent vertical rhythm between slides in a custom mobile-based experience.
I'm using getBoundingClientRect to calculate the distance between the bottom of an object, and the top of the window.
Then I use Math.floor to round down to the nearest multiple of my rem value.
Then I use this new value to create a CSS bottom margin on the flex-centered container for the alignment fix.
(Then to finish, I'd like to have this function load on $(document).ready and on window resize.)
function() {
var box = document.querySelector('.box-1');
var rect = box.getBoundingClientRect();
var bottomOrig = rect.bottom;
var htmlRoot = document.querySelector('html');
var style = getComputedStyle(htmlRoot);
var remValue = style.getPropertyValue('font-size');
var bottomNew = Math.floor(bottomOrig / remValue) * remValue;
var fix = bottomOrig - bottomNew;
$('.container-2').css("margin-bottom", "fix + 'px'");
}
Here's the fiddle.
I most likely have a syntax problem here, and would greatly appreciate help.
Thanks!

Here are some errors / corrections.
GetBoundingClientRect() is a JS function, not jQuery, so it must be used on a javascript element, not a jquery selector. Using the [0] accessor on the jquery selector (if that's how you want to get it) will give you the JS element.
Also noticed that you were trying to select the "html" tag by id, but it doesn't have any Id. Changed it to getElementsByTagName.
var offsetYOrig = $('.box-1')[0].getBoundingClientRect().bottom;
// or, without jQuery:
// var offsetYOrig = document.getElementsByClassName('box-1')[0].getBoundingClientRect().bottom;
var html = document.getElementsByTagName("html")[0];
var style = window.getComputedStyle(html);
var remValue = style.getPropertyValue('font-size');
Edit: Regarding your edit, if you need to call the javascript to recompute on window resize, you may want to try something like this. I'm not sure if it achieves what you want fully (I don't completely understand your 'snapping' requirements, but this will at least call the code again. You may still have to edit the code in the snapFunction if it doesn't suit your needs.
I added some console logs that might help you check your math as it seemed a bit problematic to me, though I was unsure how to fix it because I don't understand your goal.
function snapFunction ()
{
var box = document.querySelector('.box-1');
var rect = box.getBoundingClientRect();
var bottomOrig = rect.bottom;
var htmlRoot = document.querySelector('html');
var style = getComputedStyle(htmlRoot);
var remValue = style.getPropertyValue('font-size');
var bottomNew = Math.floor(bottomOrig / remValue) * remValue;
var fix = bottomOrig - bottomNew;
// open your browser console and check the value of these to check your math and what values you're getting
console.log("bottomOrig: " + bottomOrig )
console.log("remValue: " + remValue)
console.log("bottomNew: " + bottomNew )
// note: no quotes around your variable name fix here
$('.container-2').css("margin-bottom", fix + "px");
};
// call on load
(function() {
snapFunction();
})();
// call on resize
$( window ).resize(function() {
snapFunction();
});
I did notice that the value of your bottomNew variable was logging as "NaN" (Not a Number) so I think something is going wrong there.
I think you're getting a font-size like "36px" instead of just "36". Maybe you could try
var remValue = parseInt(style.getPropertyValue('font-size'), 10);
The 10 in that parseInt function is just specifying we want to use base 10 numbers.

I hope this will help you
Here's the edited fiddle
jsfiddle.net/ztf64mwg/82/
I just edited some variables and fixed some of the errors

I ended up jumping on HackHands and, with help, came up with a great working solution.
This will snap any vertically flex-centered object to a grid with its size set as 1rem.
All you need to do is give the object that is being measured for distance the id attribute "measure", making sure that this object is aligned correctly with a 1rem grid from the top of its own container.
Then give the parent container (or any container higher in the DOM tree) that you'd like to snap to the grid the class of "snap".
If anyone ever finds a use for this and needs further explanation, just let me know.
function snap(object){
var rect = object.getBoundingClientRect();
var bottomOrig = rect.bottom;
var htmlRoot = document.querySelector('html');
var style = getComputedStyle(htmlRoot);
var remValue = parseInt(style.getPropertyValue('font-size'));
var bottomNew = Math.floor(bottomOrig / remValue) * remValue;
var topFixPositive = bottomNew - bottomOrig;
var topFixNegative = -Math.abs(topFixPositive);
$(object).closest('.snap').css("margin-top", topFixNegative);
}
function snapClear(object){
$(object).closest('.snap').css("margin-top", "0");
}
var measureHome = document.querySelector('#measure');
snap(measureHome);
$(window).on('resize', function() {
snapClear(measureHome);
snap(measureHome);
});

Related

Cytoscape.js zigzag like edges

I am trying to create a zigzag like edges in cytoscape.js. I have found zigzag edges on github but I guess it's not yet implemented or it's abandoned, so I am trying to calculate the points but I am having some trouble.
My graph looks like this:
I assume that there are 3 cases here. One when the source is on the left of the target, other when the source and target have same x value and the third case when the source is on the right of the target.
The second case is basic and I have calculated it (just a straight line from source to the target) but I am having trouble calculating the other two cases.
I am trying to make the edges look like this:
I am using the following function to try to calculate them:
cy.elements('edge').css({'segment-distances':
function(ele){
var target = ele.target();
var source = ele.source();
var source_id = source.data().id;
var target_id = target.data().id;
var sou_pos_x = (cy.$('#' + source_id).position('x'));
var tar_pos_x = (cy.$('#' + target_id).position('x'));
var sou_pos_y = (cy.$('#' + source_id).position('y'));
var tar_pos_y = (cy.$('#' + target_id).position('y'));
var sou_tar_distance_x = (sou_pos_x - tar_pos_x);
var sou_tar_distance_y = (sou_pos_y - tar_pos_y);
// source on the left of the target
if (sou_pos_x < tar_pos_x){
return 0;
}
// source on same position as target
else if (sou_pos_x == tar_pos_x){
return 0;
}
// source on the right of the target
else{
return 0;
}
}
});
From what I have read from the documentation on segments-edges, I should have 2 points.
The calculation would have been a lot easier if you could specify coordinates for each segment point. With the way that is right now it's hard to calculate the values perpendicular to the line formed from the source to the target because those values might vary depending how far apart is the source from the target node
Can anyone help me with this issue or at least give me a guidance on how to achieve what I am looking for? I have been stuck with this problem for quite some time.
EDIT
I have added:
'source-endpoint': '180deg',
'target-endpoint': '0deg'
so now the all the edges start and end from same position, which would simplify the calculation.
EDIT 2:
This has been implemented by taxi edges like this

Slow Scroll Toggle with no jQuery?

I know its a bit to ask, but is the following possible without using jQuery? I have it running with jQuery now but it seems to be presenting performance issues. If you could help I will be most grateful. I am not lazy, just not very code knowledgable. Took me a while to even get this far.
//
// default speed ist the lowest valid scroll speed.
//
var default_speed = 1;
//
// speed increments defines the increase/decrease of the acceleration
// between current scroll speed and data-scroll-speed
//
var speed_increment = 0.01;
//
// maximum scroll speed of the elements
//
var data_scroll_speed_a = 2; // #sloganenglish
var data_scroll_speed_b = 5; // #image-ul
//
//
//
var increase_speed, decrease_speed, target_speed, current_speed, speed_increments;
$(document).ready(function() {
$(window).on('load resize scroll', function() {
var WindowScrollTop = $(this).scrollTop(),
Div_one_top = $('#image-ul').offset().top,
Div_one_height = $('#image-ul').outerHeight(true),
Window_height = $(this).outerHeight(true);
if (WindowScrollTop + Window_height >= (Div_one_top + Div_one_height)) {
$('#sloganenglish').attr('data-scroll-speed', data_scroll_speed_a).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_a * speed_increment);
$('#image-ul').attr('data-scroll-speed', data_scroll_speed_b).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_b * speed_increment);
increase_speed = true;
decrease_speed = false;
} else {
$('#sloganenglish').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
$('#image-ul').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
decrease_speed = true;
increase_speed = false;
}
}).scroll();
});
I don't see any performance issue in your code, although there is space for some optimization. And I don't think jQuery might be the problem.
First thing to notice is the CSS access.
The height attribute is very expensive to access because it causes the browser to process many rendering steps of the pipeline, as you can see in CSS Triggers.
You are retrieving the height of two elements in a scroll event, which means that they will be calculated many times. Is it really necessary?
If your #image-ul element doesn't change its height, maybe you can calculate it outside of the event only once.
In the case of the window height, I believe it won't change in the scroll event. How about to create different handlers, one for the events that need to (re)calculate the window height and another for the events that don't need that calculation?
Another noticeable point is that you set the 'data-current-scroll-speed' and the 'data-speed-increments' attribute always with the same constant value. No change, no unset. Is it really necessary?
Actually, it is not clear what you are really doing. Your performance issue might be somewhere else.

draw canvas lines to all divs with the same class name

I am making a document that will build a tree type graph through user input. I am trying to connect styled divs to the relative div they branched from with canvas lines.
I have been using .getBoundingClientRect() to get the positions, but the divs are static with inline-block, so every time a new one is added, the whole structure changes.
So, here is my attempt at a 'for loop' that is called every time a new branch is made, to re-draw all of the canvas lines.
var lines = function(){
var blocks=document.getElementsByClassName('block');
for (i=1;i<blocks.length-1;i++){
var blockDiv = blocks[i]
var offset = blockDiv.getBoundingClientRect();
var xa = offset.left+40;
var ya = offset.top+40;
var blockFrom = blockDiv.parentNode.parentNode.previousSibling;
var offsets = blockFrom.getBoundingClientRect();
var yb = offsets.top+40;
var xb = offsets.left+40;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.moveTo(xa,ya);
ctx.lineTo(xb,yb);
ctx.stroke();
}
}
Here is a jsfiddle so you can see the general structure of the divs.
When the function is called, I get no canvas lines and a console error of
166 Uncaught TypeError: blockDiv.parentNode.parentNode.previousSibling.getBoundingClientRect is not a function
I am stumped on this one and would really appreciate the help.
I am new to canvas, javascript, and coding in general so any other constructive criticism would also be greatly appreciated. :)
Vanilla js only please!
The problem is this:
Gecko-based browsers insert text nodes into a document to represent
whitespace in the source markup. Therefore a node obtained, for
example, using Node.firstChild or Node.previousSibling may refer to a
whitespace text node rather than the actual element the author
intended to get.
https://developer.mozilla.org/en-US/docs/Web/API/Node/previousSibling
Therefore, change this line:
var blockFrom = blockDiv.parentNode.parentNode.previousSibling;
to this:
var blockFrom = blockDiv.parentNode.parentNode.previousSibling.previousSibling;

What Is Wrong With This Javascript Object Code?

var Detector = function() {
var baseFonts = ['monospace', 'sans-serif', 'serif'];
var testString = "mmmmmmmmmmlli";
var testSize = '72px';
var h = document.getElementsByTagName("body")[0];
// create a SPAN in the document to get the width of the text we use to test
var s = document.createElement("span");
s.style.fontSize = testSize;
s.innerHTML = testString;
var defaultWidth = {};
var defaultHeight = {};
for (var index in baseFonts) {
//get the default width for the three base fonts
s.style.fontFamily = baseFonts[index];
h.appendChild(s);
defaultWidth[baseFonts[index]] = s.offsetWidth; //width for the default font
defaultHeight[baseFonts[index]] = s.offsetHeight; //height for the defualt font
h.removeChild(s);
}
function detect(font) {
var detected = false;
for (var index in baseFonts) {
s.style.fontFamily = font + ',' + baseFonts[index]; // name of the font along with the base font for fallback.
h.appendChild(s);
var matched = (s.offsetWidth != defaultWidth[baseFonts[index]] || s.offsetHeight != defaultHeight[baseFonts[index]]);
h.removeChild(s);
detected = detected || matched;
}
return detected;
}
this.detect = detect;
};
var d = new Detector();
alert(d.detect("Times"));
//downloaded from http://www.lalit.org/lab/javascript-css-font-detect/
This checks for fonts installed on the system by indirect method of implications. Somehow it was working perfectly on my webpage at first. I added in some more coding and now it has stopped working. I have removed all that coding and reverted the coding to that which was working initially, but it is still not working. I have tried copy-pasting it to some other pages and it is still not working there either. But when I post all this code as text and run an eval() on it, suddenly it starts working. I'm going mad. Can somebody please resolve it?
Would it not be easier just to use a font fallback within your CSS? Seems like an awful lot of JavaScript to do something that CSS can do in one line. That is assuming it's for a practical purpose.
OK I stumbled upon the real issue randomly. It keeps happening to me all the time when I use DOM references in my javascript. It's pretty little thing and is easily overlooked but thats what it makes all the more annoying and hard to catch.
I was using this code in the head section, before the body tag. Cutting it from there and pasting it after the body code resolved the issue. The reason appears to be that since this code tries to create a span object inside the body section, using this code before the body tag will end up in a logical error because by that time, the body object doesn't exist. Relocating the code beyond the body section rectified the issue and the code is running smoothly now. Thanks to all who contributed.

The logic of multiple travelling animations in javascript

I have an image of a bug. I want to make 5 copies of that image fly in from the side of the screen and bounce around the screen and bounce off the sides. I want them to all have different starting positions and different directions.
so I made some a global variables
var flyVar;
var flySpeed = 5;
var widthMax = 0;
var heightMax = 0;
var xPosition = 0;
var yPosition = 0;
var xDirection = "";
var yDirection = "";
var bugFly;
var count = 1;
var bug = "bug";
I have a function called setBugs() that I use to set the value of widthMax and heightMax depending on the size of the users screen.
I have a bugStartingPlace function to set the initial starting place for each bug. I won't post the whole function but it does the same for "bug1" through "bug5", giving them different values.
function bugStartingPlace(bugName) {
//Accepts string as argument and sets the starting position and starting direction of each bug.
if (bugName == "bug1") {
xPosition = 0;
yPosition = 100;
xDirection = "right";
yDirection = "up";
}
}
I have a function called flyBug() that does the animation and sets the position of the image. It consists of a bunch of statements like this. I know it works because I can make it work with 1 bug. The problem is doing it with 5 bugs.
function flyBug() {
if (xDirection == "right" && xPosition > (widthMax - document.getElementById("bugImage").width - flySpeed))
xDirection = "left";
<!--More flow control statements are here-->
document.getElementById("bug1").style.left = xPosition + "px";
document.getElementById("bug1").style.top = yPosition + "px";
<!-- More statements are here that set the position of the image -->
}
So, I need some way to get the animation going with the body onload() event. One problem is that setInterval does not allow functions that contain parameters. So I can't put multiple statements in the body onload event that pass "bug1" as a parameter to this function, "bug2" as a parameter to this function and so on. That's why I made the global count variable. That way, any time I need to change the name of the bug, I change the name of count and then do
bug = bug + count;
But that adds a lot of complexity. I need the name of the bug for the bugStartingPlace() function, so I need to change the value of count and also change the value of bug before I use that function. Once I use the bugStartingPlace() function, that changes the value of the global variables. Then I need to use flyBug() before I change the value of bug again.
I guess one of the problems is that I'm using global variables for direction and position even though I have multiple bugs. It works fine for one bug but not for multiple bugs.
Can anyone give me tips on how the logic of this program should work?
setInterval allows, like setTimeout, the use of parameters in the function BUT:
setInterval(funcName(param1,param2...), 100);
wont work. Youll get it to work like that:
var func = function () { funcName(param1,param2..); }
setInterval(func, 100);
To understand that part of javascript, read through dougles crockfords explanation of functions, he tells about this very clear and deep. Link to a video of him
EDIT: Sry i understood your question wrong...
The problem why it wont work is, like you figured out the global vars. You could just make bug an object. His actions will then be methods, which can contain a function and so on. If you then initialize a new bug (you can do this a thousand times then), all the vars stay in the object, without conflicting each other. This is a secure way to provide solidness of your code.
You could do it very simple, with nested functions.
Another way would be, to send the name of the bug vie parameter to the, for example, fly function. And the only work in that function with the parameter given to it.

Categories

Resources