Performance: Calculating Div Positions - javascript

I set up at test case on jsperf here: http://jsperf.com/rect-vs-offsettopmsa
Now a lot of the results there seem intuitive and make sense, however there is one thing that bothers me regarding ScrollTop and OffsetTop.
Why is it that ScrollTop is almost 3-4x faster than OffsetTop if they are both DOM Element properties? Especially since OffsetTop is also only a read-only property according to the msdn: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop

I can't say for sure — you'll have to examine source code to be sure. But the obvious reason for the offsetTop poorer performance is that it is simply more complicated to compute.
To calculate Element.scrollTop browser just needs to explore the Element you asking for — it can get all the input data it needs just from it.
But to calculate Element.offsetTop you need to calculate the element's position AND it's parent's — and then compare them to get the relative position. Hence more time needed to perform it.
Here you can find a description of how Element.offsetTop works according to spec.
If the element is the HTML body element or does not have any associated CSS layout box return zero and terminate this algorithm.
If the offsetParent of the element is null return the y-coordinate of the top border edge of the first CSS layout box associated with the element, relative to the initial containing block origin, ignoring any transforms that apply to the element and its ancestors, and terminate this algorithm.
Return the result of subtracting the y-coordinate of the top padding edge of the first CSS layout box associated with the offsetParent of the element from the y-coordinate of the top border edge of the first CSS layout box associated with the element, relative to the initial containing block origin, ignoring any transforms that apply to the element and its ancestors.
So yeah, even if it looks like a simple property, it still can trigger some calculations on the element.
UPDATED:
It looks that my answer is still incorrect because of this interface declared in the same spec:
partial interface HTMLElement {
readonly attribute Element? offsetParent;
readonly attribute long offsetTop;
readonly attribute long offsetLeft;
readonly attribute long offsetWidth;
readonly attribute long offsetHeight;
};
So, yes, both this properties are readOnly, so the fact that one is 3 times faster then other doesn't make much sense. Ignore what I've written earlier.

Related

How to use ScrollPanel with relative size

I'm trying to use a ScrollPanel of GWT in a page. Since most of the contents are in the ScrollPanel, I want it to take an as-large-as-possible part of the page and resize as the page may resize. Naturally I would want to set it a relative size, i.e. setSize("100%","100%"). However the document says it can only be set a size in absolute CSS units (e.g. "10px", "1em", but not "50%")
I cannot understand why ScrollPanel cannot take relative size in GWT. After searching and reading a lot, someone suggests just set the element's size to "100%" (see GWT Relative Width). I may give it a try but not sure if it will affect ScrollPanel's other function - as I will also control the scroll of the panel.
ScrollPanel myScrollPanel = new ScrollPanel();
myScrollPanel.setSize("2112px", "150px"); // Arbitrary width.
myScrollPanel.getElement().getStyle().setProperty("width", "100%");
So here's my questions:
(1) Why??? (this is driving me mad as I cannot understand, maybe someone with deeper understanding of the GWT inside mechanism can enlighten me)
(2) How to work around?
ScrollPanel implements RequiresResize interface, which means that it needs to get it size from its parent, or its size has to be set explicitly. Thus, you have two options.
(1) Use a parent widget that implements ProvidesResize interface - for example, LayoutPanel. It's important, however, that ProvidesResize - RequiresResize chain remains unbroken all the way from RootPanel to your ScrollPanel.
In a typical implementation, LayoutPanel (or its variant) represents your entire page. Then you can add various children to it, e.g. "header", "main view", "left menu", etc. For each child you can set the preferred size. For example:
myLayoutPanel.setWidgetTopBottom(myScrollPanel, 32, Unit.PX, 0, Unit.PX);
In this example your ScrollPanel will take all available space on a page starting from 32px at the top and all the way to the bottom. You can set its position in percentages or other units instead.
(2) You can accomplish the same layout with pure CSS. If you don't care about very old browsers, the best option is to use flexbox layout model. In this case you set display: flex on your parent widget, and flex-grow: 1 on your ScrollPanel - telling it to take all available space (unless there are other flex-grow siblings, in which case they will split the extra space).
The answer to your first question is very simple. When using the relative size for an element you are referring to the size of a parent element. So when you set height: 100% it means that your element should be 100% size of its parent.
And there are some ways to get what you want:
use the Viewport-percentage lengths - you can set height: 100vh which means 100% of the viewport height - this is the easiest way but may be not yet supported by all browsers
set both the html and body elements 100% height - this will allow you to use the relative height on child elements
use GWT DockLayoutPanel or DockPanel and add your scroll panel to the center pane - it will take all the remaining space
First, ScrollPanel is something not acting as other widget for reason I don't know why. Cannot set relative size (50%), and if I give it some style, it's lost somewhere and cannot be found from page.
My solution is to use a ResizeLayoutPanel. Andrei suggested using something ProvidesResize but requires the provide / require resize chain remain unbroken, which can be very tricky. I found this ResizeLayoutPanel "ProvidesResize to its one child, but does not RequiresResize", which is the perfect candidate for the root panel of my composite. Then, I just extend the ScrollPanel to resize itself in the "onResize" method whenever it's called by the parent ResizeLayoutPanel.
Still no answer to my first question: by ScrollPanel behave like this in the first place? I tried to find answer in its source code but was not successful (though I didn't spend enough time studying the source code).
public class My100PctScrollPanel extends ScrollPanel {
#Override
public void onResize() {
// Window.alert("on resize");
this.setWidth(this.getParent().getOffsetWidth()+"px");
this.setHeight(this.getParent().getOffsetHeight()+"px");
super.onResize();
}
}
........
compositeRoot = new ResizeLayoutPanel();
........
scrollPanel = new My100PctScrollPanel();
compositeRoot.clear();
compositeRoot.add(scrollPanel);

Changing div text layouts the whole document

I want to change the text inside a div with javascript (jQuery is ok too).
There are a few to do that:
element.innerText
element.innerHTML
element.textContent
$(element).text()
$(element).html()
But when I use the above methods, the whole document is affected and not only the div.
See chrome timeline below which refers to this fiddle
Is there a way to update the text inside the div without affecting the whole document?
Because updating the text affects the width/height of the element and the flow of the page, the entire document usually has to be laid out again whenever the DOM changes. However, you can do stuff so only part of the document needs to be re-laid out.
From http://wilsonpage.co.uk/introducing-layout-boundaries/
To be a layout boundary, the element must:
Be an SVG root (<svg>).
Be a text or search <input> field.
or:
Not be display inline or inline-block
Not have a percentage height value.
Not have an implicit or auto height value.
Not have an implicit or auto width value.
Have an explicit overflow value (scroll, auto or hidden).
Not be a descendant of a table element.
If you make the element you're updating a layout boundry, only the part of the document inside your element needs to be updated. However, keep in mind were talking about optimizations of less than a millisecond, and pre-mature optimizations are generally considered bad practice.
Note: Do not start an id attribute with a number. It may cause problems in some browsers.
http://www.w3schools.com/jquery/sel_id.asp

RSpec and Capybara: How to get the horizontal and vertical position of an element

I have added visually hidden jump links to my website, that appear when they are focused (similar to e.g. GitHub, just press Tab once on the home page).
I want to test this behaviour with Capybara. I can't check for e.g. 'visibility: true/false', because the links are not really hidden (otherwise screenreaders would't see them), but only moved out from the viewport by absolute positioning. When they are focused, they are placed at their original position in the viewport.
So I guess I have to check the X and Y coordinate, but Capybara doesn't seem to offer a native method for getting them? Is this true? And if so, how would I get them for e.g. an element '#jump_to_content'? By executing some JavaScript?
Update
I found some inspiration here: https://content.pivotal.io/blog/testing-accessibility-with-rspec-and-capybara
But this doesn't seem to work for my configration:
link.native.location
NoMethodError: undefined method `location' for #<Capybara::Poltergeist::Node tag="a">
You are correct, Capybara does not offer a native method for getting an element's position in the page, but if you have access to jQuery you can use Capybara's page.evaluate_script method to easily determine an element's position. It would look something like this:
page.evaluate_script("$('.menu > div:nth-child(1)').offset().top")
The .offset() method allows us to retrieve the current position of an element relative to the document. Contrast this with .position(), which retrieves the current position relative to the offset parent
Just note that
While it is possible to get the coordinates of elements with visibility:hidden set, display:none is excluded from the rendering tree and thus has a position that is undefined.
Using capybara's page.evaluate_script will allow you to execute some JavaScript and create an assertion off the return value:
expect(page.evaluate_script("document.querySelectorAll('a#jump-to-content')[0].style.top;")).to eq('-8000px')
According to this SO answer, the best way to use JavaScript to find the position of an element is to use getBoundingClientRect(); as it returns values that are relative to the viewport.
It feels more readable to me to find the element with Capybara instead of JavaScript, and then find its position. I'm using Selenium and Chrome.
link = find("a", text: "content")
link.evaluate_script("this.getBoundingClientRect().left;")
link.evaluate_script("this.getBoundingClientRect().right;")
link.evaluate_script("this.getBoundingClientRect().top;")
link.evaluate_script("this.getBoundingClientRect().bottom;")
Some browsers (like Chrome) also have:
link.evaluate_script("this.getBoundingClientRect().x;")
link.evaluate_script("this.getBoundingClientRect().y;")
link.evaluate_script("this.getBoundingClientRect().height;")
link.evaluate_script("this.getBoundingClientRect().width;")
You can also do
link.rect.y
link.rect.height
link.rect.x
link.rect.width
As per Nuri's answer, the best way you can go is asking the browser directly(headless browsers don't read css files, afaik).
However, rather than asking the style of the element, you might want to directly check its position by using offsetTop. So something like
expect(page.evaluate_script("document.querySelector('a#jump-to-content').offsetTop;")).to eq('-8000px')
If you need more control, you could also run it all in javascript:
expect(page.evaluate_script("document.querySelector('a#jump-to-content').offsetTop < document.querySelector('body').offsetTop;").to be_true

Retain cursor position in contenteditable

I am trying to create a richtext area using contenteditable. The parser for the highlighting returns a html string which I replace the content of the element with (perhaps ineffective, thats the way it has to be for now).
The problem with contenteditable is that the cursor position is not retained after something like this, which I can understand.
Is there a way to retain this position? I could make the parser return a element representing where the caret should be (given the cursors offset in the text) if this would aid in a solution.
Thanks in advance.
I could make the parser return a element representing where the caret should be
That would be enough. Suppose you parse the generated HTML and now you have a reference to the caret-should-be-here element in sentinel.
Use document.createRange() to create a DOM range. Let here be the new range. Call here.selectNode(sentinel) to have the range surround the element.
Call here.deleteContents() to remove the dummy element.
Call window.getSelection().removeAllRanges() to clear the current selection, which has gotten messed up by the HTML change.
Call window.getSelection().addRange(here) to put the cursor where the element used to be.
is sentinel my anchor element?
Yeah, I guess.
How do I fetch the cursor position in the string? ... I want the offset from the start of the string.
Let's start with the cursor position. The zeroth range of the window's selection should be the cursor's position. That is, the range's start and end are in the same place, at the cursor. However, these locations are expressed in a way that's geared toward DOM trees, rather than for string and offsets. They have a (start|end)Container and a (start|end)Offset. Check the DOM spec for what these mean.
You're interested in some sort of string offset. As I interpret it, this would be like, if you took a plaintext-only version of the subtree, what index corresponds to the range? There are multiple ways to define a plaintext version of a subtree. One is what the textContent property returns, where foo<br>bar gives "foobar". Some browsers define innerText, where foo<br>bar gives "foor\nbar". You've probably picked the one that you'll be using, but it's not stated in the question.
Anyway, here's an idea, but it might not be the right kind of offset for your app.
Set the window selection to a single range going from the beginning (wherever index 0 should be) to the cursor position.
Read window.getSelection().toString().length. In the browsers I've developed for, toString on a selection object gives results comparable to innerText.

Is it possible to get only hidden text of an element?

Selenium Webdriver contains a function that returns only visible text inside element. I'd want to write a function that will get only hidden text inside element (i.e. all text that isn't visible in meaning of Selenium Webdriver W3C spec). According to to this spec element is visible only if all following conditions are met:
The element must have a height and width greater than 0px.
The element must not be visible if that element, or any of its ancestors, is hidden or has a CSS display property that is none.
The element must not be visible if there is a CSS3 Transform property that moves the element out of the viewport and can not be scrolled to.
OPTIONs and OPTGROUP elements are treated as special cases, they are considered shown if and only if the enclosing select element is visible.
MAP elements are shown if and only if the image it uses is visible. Areas within a map are shown if the enclosing MAP is visible.
Any INPUT elements of "type=hidden" are not visible
Any NOSCRIPT elements must not be visible if Javascript is enabled.
The element must not be visible if any ancestor in the element's transitive closure of offsetParents has a fixed size, and has the CSS style of "overflow:hidden", and the element's location is not within the fixed size of the parent.
Is it possible to write a JS function that will return only hidden text contained inside element? Do you know of any library that contains such function? How slow will such function be?
Yes, it is possible to write such code if you are just monitoring for display: none, visibility: hidden and no size or even an absolute/relative position that is off the screen. You would have to iterate every element in the page, determine if the element is visible and, if so, collect the text from any text nodes in that element.
It will be no slower or faster than any other function that iterates every node in the document. The total time will depend upon how efficiently the iteration code is written (it can skip all children of a hidden element for example) and on how long/complicated the document is.
If you want to be able to tell the difference between text that is outside the edges of an element with overflow:hidden or elements that might be marked for visibility, but be off-screen or out of view or out of the current viewable scroll area or pieces of text that might be obscured by other elements, that would be very difficult and honestly I don't know if all of that can be figured out from pure javascript.

Categories