alert("Wrong (red): " + document.getElementById("target").getBoundingClientRect().top);
alert("Correct (blue): " + document.getElementById("wrapper").getBoundingClientRect().top);
#target {
transform: translate(20px, -20px) rotateZ(20deg);
background: red;
width: 50px;
height: 50px;
}
#wrapper {
background: blue;
display: inline-block;
}
Text
<div id="wrapper">
<div id="target">
</div>
</div>
further text
In the example above, the blue square has the exact position, the red one would have, if it had no transform. It has also exactly the space and position that the red square consumes, as an in-flow element. The position I want to get in JavaScript is the position of the blue square. My only problem is, that in my original code, there is no #wrapper and I am not able to create one. So how do I get the in-flow position of an element that might or might not be on that position (due to transform, position: relative; or others - if there are)?
Plain JS or jQuery solutions are both welcome. But I search for a rather simple/short solution, not some 50+ lines monster.
My attempts:
jQuery('#target').offset(): Takes transform into account (returns some negative number in the above example).
document.getElementById('target').getBoundingClientRect(): Same as jQuery's offset
jQuery('#target').position() with traversing through offsetParent: Might currently work, but jQuery's behavior in this regard is considered a bug or at least subject to coming changes, according to this site (if I interpret that site correctly).
Use WebKitCSSMatrix
var node = document.getElementById("target");
var curTransform = new WebKitCSSMatrix(window.getComputedStyle(node).webkitTransform);
console.log(node.offsetLeft + curTransform.m41); //real offset left
console.log(node.offsetTop + curTransform.m42); //real offset top
Browser Compatibility :
Check this answer to know how to handle browsers that isn't support WebKitCSSMatrix
Related
I write in React. On the page I have a scroll up button.
When scrolling on some blocks, it is not visible due to the fact that the colors match.
How to make it so that when it touches certain blocks of the same color as the button, it turns white.
How to track the contact of a button with a specific block? What listeners to put?
Since the button is probably position: fixed or position: absolute, the easiest solution would be to give CSS property mix-blend-mode a try, instead of listeners, as there is no accurate way of telling the position w.r.t to background.
Check this
Try to give mix-blend-mode: difference; for the scroll to top button.
Although I think in this case you will have the color yellow for the button when it overlaps blue.
Although Sanjay's answer "works", you really don't have much control over the styles you get, unless that blend-mode:difference is the look you're going for.
For more fine grained control, you need to use the IntersectionObserver API.
There's really three steps to this process:
1. Get the required options for IntersectionObserver constructor
This means you need to find out the negative margins to set as the rootMargin key. This key is part of the configuration object of your IntersectionObserver instance.
In practice you would have to find the distance of your trigger element(in your case the floating action button) to the edges of the viewport. In the code snippet below, this is done using the getDistanceToEdge() function.
It's better to do it this way instead of statically setting your margins, since it dynamically infers the margins from the position set from your styles.
2. Tell the observer what you wish to when triggered
This is the callback which will trigger the new styles on your floating button. You need to check whether your elements are intersecting with the button using the entry.isIntersecting key, and then conditionally render the style.
3. Setup the observer with elements you wish to cause the trigger
The elements that scroll up causing your floating button to change color, should be passed to the observer, using the observer.observe() function.
The following code snippet really does all the setup you need. It won't trigger unless the element is actually underneath the button (the API internally checks vertical and horizontal overlaps).
For some reason I can't get it to work on iframes embedded in browsers. It could be something to do with the negative margins and multiple viewports.
It works perfectly on normal full page websites, as shown in this hosted link.
One little caveat is that consecutive elements that trigger the observer, will cause each other to cancel out, since the button enters the second area before fully leaving the first one. I'll leave it to someone else to figure out the specifics there.
// get the element that reacts to background elements
const fabElement = document.getElementById("fab");
// what do you want to do when it overlaps?
function reactToOverlap(entries) {
entries.forEach((entry) => {
if (entry.isIntersecting) fabElement.classList.add("triggered");
else fabElement.classList.remove("triggered");
});
}
const observerOptions = {
rootMargin: getDistanceToEdge(fabElement)
.map((val) => `-${val}px`)
.join(" "), // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin for syntax
};
const observerInstance = new IntersectionObserver(
reactToOverlap,
observerOptions
);
// what elements do you want to trigger the change?
const elementsToTriggerFab = document.querySelectorAll(".triggeringContent");
elementsToTriggerFab.forEach((el) => observerInstance.observe(el));
/* Represents how far the element is from the edge of the browser, in all directions. */
function getDistanceToEdge(element) {
const elementBounds = element.getBoundingClientRect();
// top and left margins are identical to the pixel coordinates
const top = elementBounds.top;
const left = elementBounds.left;
// bottom and right need to be subtracted from the viewport bounds
const bottom = window.innerHeight - elementBounds.bottom;
const right = window.innerWidth - elementBounds.right;
return [top, right, bottom, left];
}
body {
font-size: 3rem;
background-color: white;
margin: 0;
padding: 0;
}
#fab {
background-color: blue;
color: white;
border-radius: 50%;
position: fixed;
padding: 1rem;
bottom: 50px;
right: 50px;
transition-property: background-color, color;
transition-duration: 0.5s
}
#fab.triggered {
background-color: white;
color: blue;
}
.normalContent {
color: green;
border: 10px solid green;
height: 100vh;
}
.triggeringContent {
height: 300px;
background-color: blue;
color: white;
}
.triggeringContent.avoidFab {
margin-right: 200px;
}
<div id="fab">Up</div>
<div class="normalContent">Other Content</div>
<div class="triggeringContent">Triggerring Content</div>
<div class="triggeringContent avoidFab">Triggerring Content</div>
<div class="normalContent">Problems coming up</div>
<div class="triggeringContent">Triggerring Content</div>
<div class="triggeringContent">Triggerring Content(Error)</div>
<div class="normalContent">Other Content</div>
<div class="triggeringContent avoidFab">Triggerring Content</div>
<script type="module" src="/main.js"></script>
Shoutout to cloned's comment for giving the idea for the solution.
The values returned by getComputedStyle are resolved values. These are usually the same as CSS 2.1’s computed values, but for some older properties like width, height, or padding, they are instead the same as used values.
-- MDN: window.getComputedStyle() notes
So is it currently possible to get the resolved value of height as it was specified in stylesheet?
I.e. know that some element's computed ..px (= used) height was specified as height: 100%; in style sheet? (100% being the resolved value in question.)
Is there some new specification regarding this problem in consideration?
Edit 2020-08-17: see very similar question and excellent answer from 2012: Is there a cross-browser method of getting the used css values of all properties of all elements?
(Sadly, noting seem to ave changed since then.)
No, there is no specification or functionality that supports or enables this method. There are plenty of ways to go the other direction, including...
getBoundingClientRect()
offsetHeight
getComputedStyle
... but none that will retrieve the exact percentage specified in the CSS, unfortunately.
You can try, as I've done below.
RegEx
If your height is specified as an inline style, you could RegEx it out of the attribute like so1:
let elem = document.getElementsByTagName("div")[0];
let re = /(?<=\height:\s+?)[^\;]+/i;
console.log(re.exec(elem.getAttribute("style"))[0]);
<div style="color: black; height: 100%; background: white;"></div>
This is terrible practice, though, and could be janky if there are multiple width declarations (which should never happen, but we're already in "bad code land", so why not?). Of course, this ignores the fact that inline styles should generally be avoided in the first place, so this probably won't apply to you.
Bounding calculations
It's also possible to calculate the value yourself by comparing the height of the element with the height of its parent.
let elem = document.getElementById("inner");
let pare = elem.parentElement;
let hrat = `${100*(elem.offsetHeight / pare.offsetHeight)}%`;
console.log(hrat);
#container {
height: 300px;
width: 400px;
background: repeating-linear-gradient(45deg, rgba(255,0,0,0.35), rgba(255,0,0,0.35) 10px, rgba(255,0,0,0.1) 10px, rgba(255,0,0,0.1) 20px), linear-gradient(white, white);
}
#inner {
height: 200px;
width: 400px;
background: darkgreen;
mix-blend-mode: hue;
}
<div id="container">
<div id="inner"></div>
</div>
If you add borders, margin, or padding, or the element adapts to the size of its content, though, this calculation will be incorrect.
Conclusion
In conclusion, everything is jank.
In my opinion, you'd be better off not fighting with CSS and JavaScript to coerce the value from the available information, and working out a way to do without the value. I've tried to do this kind of thing many times, so be forewarned: this way lies madness.
1RegEx lookbehinds are not even remotely close to being fully supported, so shouldn't be used in production.
You could read the stylesheet rule itself. If you know the selector that sets an elements width/height you can do this:
.box {
width: 12vw;
background: red;
}
<div class="box">red</div>
var sheet = document.styleSheets[0];
var rules = sheet.rules;
for (var i = 0; i < rules.length; i++) {
if (rules[i].selectorText == '.box') {
console.log(rules[i].style.width);
}
}
This will give you the 12vw you're looking for.
Depending on how your elements are defined, you could in theory create a helper function that gets these values for you by looping through the elements classList.
if I have element,and I want to translate it for 50px more than the current value.Why I can't do something like this?
element.style.transform="translateX(+=50px)"
Is there a way to do something like that?
If your transform is set in a stylesheet, element.style.transform will return an empty string. You must use getComputedStyle.
To get the x translation as an integer use:
var transform = parseInt(window.getComputedStyle(element, null).transform.split(',')[4]);
Then use this to increment by 50 pixels:
element.style.transform = "translateX(" + (transform + 50) + "px)"
Here is a fiddle: https://jsfiddle.net/uhuh9r64/
JS does not understand CSS and vice versa, but CSS can solve this on its own.
Unknown to most people, the ECMAScript (proper name for JS) specification and the CSS and DOM standards are maintained by different groups and have little ability for interoperation. Most interfacing between them happens with JS invoking the APIs, but JS does not typically consider or care what API it invokes.
That is, JS has no explicit support for CSS and sees this as a string.
That doesn't matter when you can transform elements using pure CSS, without JS needing to be involved (although you can apply transforms from JS). A simple position transform, even in 2D, can easily move an element down by 50px.
The translateX() is a css function used to move the element horizontally on the plane. This transformation is define by a length that define how much it moves in horizontally. The sample code example is for you in the bellow.....
exm.html
<div>
<p>welcome</p>
<p class="transformed">bar</p>
<p>thanks</p>
</div>
exm.css
p {
width: 50px;
height: 50px;
background-color: teal;
}
.transformed {
transform: translateX(10px);
background-color: blue;
}
I have this html and css: http://jsfiddle.net/b7rDL/6/
HTML:
<div class="text-block" contenteditable="true" spellcheck="false">Some Text</div>
css:
.text-block {
resize: none;
font-size:40px;
border: none;
line-height: 1;
-moz-appearance: textfield-multiline;
-webkit-appearance: textarea;
min-width: 30px;
overflow: visible;
white-space: nowrap;
display: inline-block;
}
This code allows me to write text with no width limit or height limit. It displays no scroll bar and it grows with the text. Those are basically the features I need.
How can I convert this to regular textarea that will act the same? I want this to work on browser that doesn't implemented "contenteditable". Therefore I want to replace the div with textarea or other basiv element. How can I do it? (I don't mind using JavaScript).
How can I disable the spellchecker? spellcheck=false doesn't work. In the example, when I focus on the text box, I get that buggy red line. I am using Firefox.
How can I get rid of the border when I am focused? - SOLVED
I don't mind using JavaScript to solve those issues.
Any answer for those questions will help me.
Thanks
UPDATES:
#Oylex helped me with 3
#1
Working fiddle is here: http://jsfiddle.net/d9H9w/11/ (tested in IE8, Chrome and Firefox)
What you need is to set the width and height attributes as a user is typing within a text box.
Height
This is pretty straightforward:
Get the content of the textarea
Match for newline characters
Set the height to total number of newline characters(plus one for the first line and 1.5 for wiggle room) in em's.
Setting the height in em's makes this font-size agnostic, so it'll work with multiple font-sizes.
function getNewlines(){
// get the value(text) of the textarea
var content = textEl.value;
//use regex to find all the newline characters
var newLines = content.match(/\n/g);
// use the count of newlines(+1 for the first line + 1 for a buffer)
// to set the height of the textarea.
textEl.style.height = ((newLines && newLines.length || 0)+2.5)+'em';
};
Width
This is fairly easy, too, with one gotcha.
Get the content of the textarea
Split on newline characters to get an array consisting of lines of the textarea
Sort to get the longest line
Set the width to the length of the longest string in em's, multiplied by about .6(emRatio in my code), plus 2 ems for buffer space.
That last part is the kicker. The 'em' measurement is supposed to be a square representing the width and height that a single character takes up. This doesn't take kerning into account, so the height of a char is usually accurate, but the width is dependent on the chars around it. So, by guess and check, I figured that .6 em is about the average width of a character after kerning. .6 is pretty close, so I add 2 ems to the width for a bit of buffer space.
var emRatio = .6;
function longestLine(){
// get the value(text) of the textarea
var content = textEl.value;
// split on newline's. this creates an array, where each item in
// the array is the text of one line
var a = content.split('\n');
// use a sorting function to order the items in the array:
// longest string to shortest string
a.sort(function(a,b){return b.length - a.length});
// use the longest string * the emRatio to set the width
// Due to kerning, the letters aren't ever really 1em x 1em
// So I'm multiplying by an approximate ratio here (guess and check)
textEl.style.width = (a[0].length * emRatio + 2)+ 'em';
};
Existing problems with this implementation
To support resizing during long-held key presses, an onkeydown handler has to be included as well(this is not optimal for all cases that don't include long key presses)
All things considered, I think this fits what you need.
EDITS
Instead of having emRatio be .7, I changed it to .6 and added a buffer of 2 ems to the width. This addresses both issues #Naor mentioned in his comments.
I've updated the fiddle link and the Width section to reflect the changes.
EDIT 0
Request #1 Update
Working Solution: http://jsfiddle.net/7aeU2/
JQuery
$(function() {
// changes mouse cursor when highlighting loawer right of box
$("textarea").mousemove(function(e) {
var myPos = $(this).offset();
myPos.bottom = $(this).offset().top + $(this).outerHeight();
myPos.right = $(this).offset().left + $(this).outerWidth();
if (myPos.bottom > e.pageY && e.pageY > myPos.bottom - 16 && myPos.right > e.pageX && e.pageX > myPos.right - 16) {
$(this).css({ cursor: "nw-resize" });
}
else {
$(this).css({ cursor: "" });
}
})
// the following simple make the textbox "Auto-Expand" as it is typed in
.keyup(function(e) {
// the following will help the text expand as typing takes place
while($(this).outerHeight() < this.scrollHeight + parseFloat($(this).css("borderTopWidth")) + parseFloat($(this).css("borderBottomWidth"))) {
$(this).height($(this).height()+1);
};
});
});
Request #2 Update
Also, here's a good explanation of why you can't outright disable spell check.
This does not belong to the realm of CSS (which is optional
presentational suggestions). It is not about stylistic features of
rendering data but about processing data interactively.
On browsers that support “spell checking” (which may involve grammar
and style checks), the HTML attribute spellcheck or the corresponding
IDL (DOM) attribute, settable in JavaScript, is effective.
In practice, those browsers tend to have “spelling checking” enabled
by default for textareas only, and as textareas normally contain human
language texts, turning it off does not sound useful. It is in any
case user-controllable (the user can switch it off or select
language).
via https://stackoverflow.com/a/9209791/1085891
Request #1
Simple Solution is pretty straight forward.
Working example: http://jsfiddle.net/b7rDL/12/
JQuery
$("#Solution0").keyup(function(e) {
while($(this).outerHeight() < this.scrollHeight) {
$(this).width($(this).width()+50);
};
});
HTML
<textarea id="Solution0" rows="1" style="height: 1.2em;"></textarea>
Fancier solution that will require some updating if you want the
width, rather than the height, to expand. Still, it's pretty nice.
http://jsfiddle.net/edelman/HrnHb/
https://github.com/ultimatedelman/autogrow
Other solutions - I know these all expand height. Let me know if you need width implementation of one of the below solutions.
http://bgrins.github.com/ExpandingTextareas/
http://blogs.sitepointstatic.com/examples/tech/textarea-expander/index.html
http://code.google.com/p/xautoresize-jquery/downloads/list
http://www.impressivewebs.com/textarea-auto-resize/
http://www.technoreply.com/autogrow-textarea-plugin-3-0/
Request #2
spellcheck="true" should work as described in the Mozilla docs: Controlling spell checking in HTML forms. It works for me in my first simple example running in Firefox 13.0.1. What version are you running?
for #3, the css option you are looking for is: outline: none;
I was having trouble figuring out the bounds of the textarea's content, so with this approach I'm copying the content of the textarea into a similarly styled p element, which is set to float: left; and then resizing the textarea based on the size of the p. This handles both width and height.
I've tested on Mac 10.8.1 FF 18.0, Safari 6.0, Chrome 25.0.1362.0 canary, iOS Safari 6.0.1 and iOS Simulator 5.1 (272.21). I don't have a PC or IE handy.
http://jsfiddle.net/b7rDL/34/
HTML
<textarea id="tx" class="text-block" spellcheck="false"></textarea>
<p id="dupe" class="text-block"></p>
CSS
.text-block {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
resize: none;
font-size:14px;
border: none;
line-height: 1;
min-width: 30px;
overflow: hidden;
white-space: pre;
display: block;
outline: none;
width: 30px;
height: auto;
border: 1px solid #ddd;
background-color: #f7f7f7;
}
#dupe {
float: left;
display: none;
width: auto;
height: auto;
}
I added a background and border so I could see what's going on.
JavaScript
// no `var` so they are global and easier to work
// with in the inspector when using jsFiddle
$tx = $('#tx');
$dupe = $('#dupe');
lineH = Number($tx.css('line-height').replace('px',''));
update();
$tx.on('keydown', function() {
setTimeout(update, 0);
});
$tx.on('propertychange input keyup change', update);
function update() {
$dupe.text($tx.val());
$tx.css({
width: $dupe.width() + 7,
height: $dupe.height() + lineH
});
}
// not sure if this is needed, leaving it because
// I don't have many browsers to test on
$tx.on('scroll', function() {
tx.scrollLeft = 0;
tx.scrollTop = 0;
});
I'm adding extra space on the right and at the bottom because it seems to perform more consistently that way. Also, in the HTML, the wrap="off" is necessary for the version of Firefox, I'm using.
I got some good tips from this blog post.
Request #2
Working Demo
<body spellcheck="false">
<div class="text-block" contenteditable="true">
Some Text SpellCheck</div>
Hi Naor, The only problem with this thing is it will disable the spellcheck for all the elements in the <body> tag. If it doesn't matter you then you can go with it.
Your question is really interesting and challenging really liked it. I hope this may help you..!!
Best efficient way which was worked for me while I did something very close in the past was creating hidden out of flow div with the same exactly styles as the textarea has. And than setting out the timeout to update its html source based on information from textarea. This sounds bit scary but yet, after some testing and playing around nothing was better, that was already suggested, so just my variant.
http://jsfiddle.net/f2gAD/16/
and jQuery based script:
var textarea = $('textarea'),
textBlock = $('div.text-block'),
interval, value, freq = 10,
doTextAreaAdjust = function(){
textarea.css({
height: textBlock.outerHeight(),
width: textBlock.outerWidth()
});
};
doTextAreaAdjust();
textarea
.focus(function(){
interval = window.setInterval(
function() {
value = textarea.val().replace(/(\r\n|\n|\r)/gm, '[rnnr]');
value = value.replace(/</gm, '||'); // break html tags
value = value.replace(/\[rnnr\]/gm, '<br>');
value = value + '|'; // or <span>|</span> for better pixel perfect
textBlock.html(value);
doTextAreaAdjust();
}, freq
);console.log(interval);
})
.blur(function(){
window.clearInterval(interval);
});
For performance wise did it as self starting/stopping timeout on focus/blur, though here is yet some workaround is required. While testing in Chrome noted that interval not properly stopped if you made blur by clicking on another tab. So probably replacement for self calling function into the setTimeout will be better.
It works more or less fine in IE 7-8 which suppose the main targets but still some text jumps time to time occur, while for others it is okay, but guess you will use editable feature for modern browsers. Would recommend use modernizer for its detection.
Working here
http://jsfiddle.net/H2GSx/
Code here:
HTML:
<div style="overflow: scroll; width: 200px; height: 100px;">
<div class="text-block" contenteditable="true" spellcheck="false">Some Text</div>
</div>
CSS:
.text-block {
resize: none;
font-size:40px;
border: none;
line-height: 1;
-moz-appearance: textfield-multiline;
-webkit-appearance: textarea;
min-width: 30px;
overflow: visible;
white-space: nowrap;
display: inline-block;
}
I have a curved div created with css3 border radius (the image part). I have text lines next to it that I would like to align 20px or so off the curve, like so (cant post images, cant remember old login):
The trick is the curve changes depending on the window size, so I'd like to be able to calculate the points on the curve that the text should offset from, essentially creating a really manual text-wrap.
Ultimately I need to be able to calculate and update with javascript.
(edited to add per comment below): The curve css for purposes of demonstration is
border-bottom-left-radius: 316px 698px;
but that is calculated based on page size by a script.
Also, good to mention, is I have no requirement to support IE or FireFox at all--just webkit (standalone kiosk application).
As suggested in comment by duri, you can use circle equation:
http://www.wolframalpha.com/input/?i=%28x-5%29^2%2B%28y%2B4%29^2%3D25 (where center is in 5;-4 r=5)
However, I was using Bezier's Curves for drawing in Javascript. They are very flexible and consist or 2 vectors, the curve made by them starts in first vector's initial point and finish in second vector's.
More: http://en.wikipedia.org/wiki/B%C3%A9zier_curve
(Notice that for drawing part of the circle vector will be perpendicular)
Okay, I've played with it for a while. This is an early concept; it is somewhat inefficient, doesn't work with scrollbars and so far it works (more or less) only in Internet Explorer 9 and Firefox 5 (I didn't test other versions of these browsers). I skipped that mathematical stuff and did it another way - I use document.elementFromPoint to find out where the curve ranges. This is why it doesn't work in Chrome - its implementation of elementFromPoint doesn't respect border-radius (look at this example); hence I'll probably have to remodel the whole script. Nevertheless I show you what I created because it could be a good inspiration for another people who are willing to help you. I'll try to improve my script; I'll let you know when I make some progress.
The script can be found at http://94.136.148.82/~duri/teds-curve/1.html
To work out the position of a point along a circle you can use the formula:
c^2 = a^2 + b^2
where c = radius, a is verticle distance from center, b is horizontal distance from center.
So knowing this, I constructed a very contrived example for you to review. Please note there are a couple of things to help increase performance such as caching the radius squared but I left it out to avoid complicating the demo.
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<style>
#wrapper { position: relative; }
#curved {
position: absolute;
left: 200px;
-moz-border-radius-bottomleft: 200px;
-webkit-border-bottom-left-radius: 200px;
border-bottom-left-radius: 200px
border: 1px solid red;
padding: 100px;
background: red;
}
#magiclist { padding-top: 15px; width: 325px; list-style-type: none; }
li { text-align: right; }
</style>
<script>
$(function() {
/* c^2 = a^2 + b^2, c = radius, a = verticalShift, b = horizontalShift */
/* Therefore b = sqrt(c^2 - b^2), so now we can calculate the horizontalShift */
var radius = 200;
var verticalShift = 0;
var horizontalShift = 0;
/* iterate over the list items and calculate the horizontalShift */
$('li').each(function(index, element) {
/* calculate horizontal shift by applying the formula, then set the css of the listitem */
var horizontalShift = Math.sqrt(Math.pow(radius,2) - Math.pow(verticalShift,2));
$(element).css('padding-right', horizontalShift + 'px');
/* need to track how far down we've gone, so add the height of the element as an approximate counter */
verticalShift += $(element).height();
});
});
</script>
</head>
<div id="wrapper">
<div id="curved">test</div>
<ul id="magiclist">
<li>one</li>
<li>one</li>
<li>one</li>
<li>one</li>
<li>one</li>
<li>one</li>
<li>one</li>
<li>one</li>
<li>one</li>
<li>one</li>
</ul>
</div>
</html>
If you can use jQuery I have created a jQuery pluging called jCurvy that will allow you to position elements along a bezier curve.
You would need to adjust the parameters that you are passing into the curve function in order to match the changing curve, which may be tricky. How much is your curve changing?