Why is the scrollWidth of a valid text area undefined? - javascript

I have found many answers suggesting I use scrollWidth and scrollHeight to get the content width and height, respectively, of a text area. However, my code returns the scrollWidth as undefined. Any idea why this could be the case?
function changewidth(o) {
if ($(o).val().length>8)
{
var current = $(o).css("width");
console.log($(".name").scrollWidth+'px');
$(o).css("width",$(".name").scrollWidth+'px');
}
}
And this javascript function is being called by this text area:
<textarea rows="1" onkeyup="changewidth(this)" class="name" type="textarea" placeholder="name"></textarea>
The console prints undefinedpx. Based on research, I have tried to alter the CSS and currently have the following:
.name {
width: 100px;
margin: 0;
padding: 0;
border: none;
font-size: 20px;
color: #00B45E;
resize: none;
overflow: hidden;
}

You have to try this, No need to wrap currently passed object into $() unless you need it.
function changewidth(o) {
if ($(o).val().length > 8) {
var current = $(o).css("width");
console.log(o.scrollWidth + 'px'); // o.scrollWidth
$(o).css("width", o.scrollWidth + 'px'); // use object as o direclty
}
}
With pure Javascript
function changewidth(o) {
if (o.value.length>8)
{
var current = o.style.width;
console.log(o.scrollWidth+'px');
o.style.width = o.scrollWidth+'px';
}
}
Fiddle
With pure javascript
Demo
scrollWidth

scrollWidth only applies to plain Javascript, it seems you are using it on jQuery.
instead of $(".name").scrollWidth try changing it to document.getElementsByClassName("name").scrollWidth

Related

How to make this div scrollTop value match that of the textarea?

I'm using a div to format and display the text from a textarea of equal dimensions and I need them to be permanently in sync. However, I haven't been able to synchronize their respective scrollTops after the input text goes past the bottom of the textarea.
My process has been similar to the one described here, however I can't get his solution to work on my project.
Here's a demo and snippets of the minimum relevant code:
<section>
<div class="input-text__container">
<div id="input-text--mirror" class="input-text"></div>
<textarea
id="input-text--original"
cols="30"
rows="6"
autofocus
class="input-text"
placeholder="Enter your text here"
autocomplete="off"
autocorrect="off"
spellcheck="false"
></textarea>
</div>
<section>
#import url('https://fonts.googleapis.com/css2?family=Inter:wght#400;500&display=swap');
html {
font-size: 62.5%;
box-sizing: border-box;
scroll-behavior: smooth;
}
*,
*::after,
*::before {
margin: 0;
padding: 0;
border: 0;
box-sizing: inherit;
vertical-align: baseline;
}
body {
height: 100vh;
}
section {
display: flex;
flex-direction: column;
min-height: 100%;
padding: 1rem;
}
.input-text__container {
width: 100%;
position: relative;
flex: 1;
}
.input-text {
width: 100%;
height: 100%;
position: absolute;
font-size: 3.2rem;
overflow-wrap: break-word;
font-family: "Inter";
}
#input-text--mirror {
background-color: #e9ecf8;
color: #0a3871;
overflow: hidden;
}
#input-text--original {
background-color: transparent;
-webkit-text-fill-color: transparent;
resize: none;
outline: none;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
#input-text--original::placeholder {
color: #e9ecf8;
-webkit-text-fill-color: #052051;
}
#input-text--original::selection {
-webkit-text-fill-color: #ffffff;
}
.invalid {
font-weight: 400;
color: #ff0000;
}
#input-text--original::-webkit-scrollbar {
display: none;
}
let invalidInput = false;
const patterns = {
valid: "a-z ",
invalid: "[^a-z ]",
linebreaks: "\r|\r\n|\n",
};
const textIn = document.querySelector("#input-text--original");
const mirror = document.querySelector("#input-text--mirror");
function validateInput(string, className) {
let anyInvalidChar = false;
// Generate regular expressions for validation
const regExpInvalids = new RegExp(patterns.invalid, "g");
const regExpLinebreaks = new RegExp(patterns.linebreaks);
// Generate innerHTML for mirror
const mirrorContent = string.replace(regExpInvalids, (match) => {
if (regExpLinebreaks.test(match)) {
return "<br/>";
} else {
anyInvalidChar = true;
return `<span class=${className}>${match}</span>`;
}
});
// Update mirror
mirror.innerHTML = mirrorContent;
return anyInvalidChar;
}
textIn.addEventListener("input", (e) => {
const plain = textIn.value;
const newInputValidity = validateInput(plain, "invalid");
mirror.scrollTop = textIn.scrollTop;
});
textIn.addEventListener(
"scroll",
() => {
mirror.scrollTop = textIn.scrollTop;
},
{ passive: true }
);
On a desktop screen typing the first 8 natural numbers in a column should be enough to reproduce the issue.
The last thing I checked, but perhaps the most relevant so far was this. It seems to deal with the exact same issue on React, but I'm afraid I don't know how to adapt that solution to Vanilla JavaScript, since I'm just starting to learn React. Please, notice, I'm trying to find a solution that doesn't depend on libraries like jQuery or React.
Besides that, I tried the solution described in the aforementioned blog, by replacing return "<br/>"; with return "<br/> "; in my validateInput function but that didn't work. I also added a conditional to append a space to plain in const plain = textIn.value; in case the last char was a linebreak, but I had no luck.
I also included console.log commands before and after mirror.scrollTop = textIn.scrollTop; in the textIn scroll handler to track the values of each scrollTop and even when they were different, the mirror scrollTop wasn't updated. I read it might be because divs weren't scrollable by default, but adding "overflow: scroll" to its styles didn't fix the problem either.
I read about other properties related to scrollTop, like offsetTop and pageYOffset, but they're either read-only or not defined for divs.
I've reviewed the following posts/sites, too, but I've still haven't been able to fix this problem.
https://codepen.io/Goweb/pen/rgrjWx
https://stackoverflow.com/questions/68092068/making-two-textareas-horizontally-scroll-in-sync
Scrolling 2 different elements in same time
React : setting scrollTop property of div doesn't work
sync scrolling of textarea input and highlighting container
.scrollTop(0) not working for getting a div to scroll to the top
How to attach a scroll event to a text input?
I no longer remember what else I've reviewed, but nothing has worked and I no longer know what else to do. Thank you for your attention and help.
After trying to replicate the solution for a React app that I mentioned in the post, using vanilla JavaScript (demo here), I tried to apply that to my own project and all I had to do was adding a <br> tag to the mirror in the end of my validateInput function. That is: mirror.innerHTML = mirrorContent + "<br>";.
Besides that, updating the mirror's scrollTop every time the input event on the textarea was triggered was not needed. Neither was it to pass the { passive: true } argument to the scroll event.
The modified code is here:
function validateInput(string, className) {
let anyInvalidChar = false;
// Generate regular expressions for validation
const regExpInvalids = new RegExp(patterns.invalid, "g");
const regExpLinebreaks = new RegExp(patterns.linebreaks);
// Generate innerHTML for mirror
const mirrorContent = string.replace(regExpInvalids, (match) => {
if (regExpLinebreaks.test(match)) {
return "<br/>";
} else {
anyInvalidChar = true;
return `<span class=${className}>${match}</span>`;
}
});
// Update mirror
mirror.innerHTML = mirrorContent + "<br>";
return anyInvalidChar;
}
textIn.addEventListener("input", (e) => {
const plain = textIn.value;
const newInputValidity = validateInput(plain, "invalid");
});
textIn.addEventListener("scroll", () => mirror.scrollTop = textIn.scrollTop);

Get target style from Javascript while transitioning

During a transition, is there a way to see what the target value is for a style rule which is under transition? window.getComputedStyle gets the interpolated value, and element.style only looks at the style attribute (I think).
Here's a demo of the problem;
I'd like to retrieve the target height value of 1200px during the transition:
https://jsfiddle.net/EoghanM/xz5s3ua6/5/
setInterval(function() {
document.body.children[0].innerHTML = getComputedStyle(document.body.children[0])['height']
}, 300)
setTimeout(function() {
document.body.children[0].classList.toggle('changing')
}, 1000)
div {
height: 400px;
border: 1px solid red;
width: 200px;
transition: height 100s linear;
}
div.changing {
height: 1200px;
}
<div></div>
How about using getComputedStyle on a new instance of changing class?
You can create a div with the class changing and then use getComputedStyle to get the class properties (considering that the height of changing class will be the final height after the transition of your div) like this:
<div class="changing" id="new-changing-div"></div>
and get it's properties:
const element = document.querySelector('#new-changing-div');
const heightAttribute = window.getComputedStyle(element).getPropertyValue('height');
I'm 99% sure all of the DOM properties and methods for requesting the element's height (clientHeight, offsetHeight, getBoundingClientRect, etc.) will only give you the interpolated value. Another solution may be to read the value from the CSS stylehseet itself using the CSSOM.
In the code below, we search through the document's stylesheets checking if the selector exists in a rule declaration and if it does, return the value of the property we're looking for. You can console.log() various parts of the stylesheets and rules below to see how the browser stores the information as objects.
Of course this is a simple example based on a simple test case. There could be multiple rules using the same selector, but this will only find the first occurrence. The solution would need to be more robust to find the exact rule you're looking for.
function getCssRuleValue(selector, property) {
const styleSheets = document.styleSheets;
let styleSheetsLen = styleSheets.length;
while (styleSheetsLen--) {
const styleSheet = styleSheets[styleSheetsLen];
const rules = styleSheet.rules;
let rulesLen = rules.length;
while (rulesLen--) {
const rule = rules[rulesLen];
// The passed-in selector text is found in the rule text
if (rule.cssText.indexOf(selector) > -1) {
return rule.style[property];
}
}
}
// The selector/property was not found in any document stylesheets
return -1;
}
setInterval(function() {
document.body.children[0].innerHTML =
getComputedStyle(document.body.children[0])['height']
+ '<br>' +
getCssRuleValue('.changing', 'height')
}, 300)
setTimeout(function() {
document.body.children[0].classList.toggle('changing')
}, 1000)
div {
height: 400px;
border: 1px solid red;
width: 200px;
transition: height 100s linear;
}
div.changing {
height: 1200px;
}
<div></div>

Set height of a div in vue js

I am using vue js . I want to set one div based on another div height, with pure javascript. the problem I am facing is I can't set the height using pure java script. but I can set it using jquery. can any one please help me to change this jquery to javascript . the code I am using is given
Vue.nextTick(function () {
var offsetHeight = document.getElementById('filterSection').offsetHeight;
$(".searchResultSection").css("max-height",`calc(100% - ${offsetHeight}px)`);
});
I need to change the jquery part into java script.
In fact, computed properties (https://v2.vuejs.org/v2/guide/computed.html#Computed-Properties) is the perfect option to solves your problem:
Declare a computed property who will return the filterSectionHeight
export default {
name: "App",
computed: {
filterSectionHeight() {
const filterSectionDOM = document.getElementById("filterSection");
return filterSectionDOM ? filterSectionDOM.offsetHeight : 0;
},
}
};
Define your div filterSection and searchResultsSection in your component (or App component), don't forget to add a :style attribute who handle the dynamic max-height provided to .searchResultsSection in your template
<div id="filterSection"></div>
<div class="searchResultsSection"
:style="{
'max-height': `calc(100% - ${filterSectionHeight}px)`
}">
</div>
Set height of each div to 100% in your css
#app {
height: 600px;
width: 100%;
}
#filterSection {
height: 100%;
background: rebeccapurple; //https://en.wikipedia.org/wiki/Eric_A._Meyer
}
.searchResultsSection{
height: 100%;
background: rebeccapurple;
opacity: 0.6;
}
You will find a full demo here > https://codesandbox.io/s/1rkmwo1wq
Not sure about the context of this change, but I'll try to give you some solutions based on best practices on how to achieve that:
If parent element has a fixed height, then children with height: 100% will adopt parents height.
.parent {
height: 200px;
}
.child {
height: 100%;
}
Use a vue-directive for DOM manipulation: https://v2.vuejs.org/v2/guide/custom-directive.html
Use v-bind:style="height: calc(100% - ${offsetHeight}px)\" on the element that you want to achieve the height!
You need to use the javascript DOM-style to do this.
Try this.
Vue.nextTick(function () {
var offsetHeight = document.getElementById('filterSection').offsetHeight;
var x = document.getElementsByClassName("searchResultSection");
x[0].style.maxHeight = "calc(100% - ${offsetHeight}px)";
});

element.scrollTop always returns 0

I'm trying to get the scrollTop position of an element, but it always returns 0. What am I doing wrong, and how can I fix it?
JSFiddle
var inner = document.getElementById('inner');
window.addEventListener('scroll', function() {
console.log(inner.scrollTop);
})
#outer {
background-color: tan;
height: 1000px;
}
#first {
background-color: bisque;
height: 200px;
}
#inner {
background-color: aquamarine;
height: 100px;
}
<div id="outer">
<div id="first"></div>
<div id="inner">scrollTop always returns 0</div>
</div>
As #FelixKling pointed out in the comments:
inner.offsetTop is what to use for this. scrollTop returns the amount you scrolled in that particular container. So because inner doesn't have a scrollbar, it never scrolls, and therefore scrollTop is 0.
But offsetTop, on the other hand, returns the distance of the current element relative to the top of the offsetParent node.
So the formula to get the amount scrolled of an element based on window, would be:
inner.offsetTop - document.body.scrollTop;
For some reason, removing 'height: 100%' from my html and body tags fixed this issue.
I hope this helps someone else!
I am not sure why, but the only thing I could get to work was using window.pageYOffset:
window.addEventListener('scroll', function() {
let scrollPosition = window.pageYOffset;
if (scrollPosition >= 30) {
headerContainer.classList.add('lg:bg-green-lightest');
} else {
headerContainer.classList.remove('lg:bg-green-lightest');
}
});
I found a simplest way to do this by adding eventListener to window object this way:
window.addEventListener("click", () => {
let value = window.scrollTop;
console.log(value);
});
This isn't the cleanest way, but It's the only way I got it work as it should.
Removing overflow: hidden from my html tag fixed this for me.

How to show the value of Range Slider on the thumb of the Slider?

This is an odd request; However, what would be the best method to display the value of an HTML5 range-slider on the thumb of the slider?! the thumb will be animated onLoad so it must follow the thumb; moreover, this will be displayed for iPad
EXAMPLE:
<input class="range-consideration" type="range" name="points" min="1" max="10" ng-model="rangeConsiderations">
I too have had a lot of trouble figuring out a way to display the value of a range slider on its thumb. When working this out, I thought of three methods:
1. Combining multiple pseudo-elements
(Spoiler: Doesn't work - Read BoltClock's comment on the accepted answer of this thread)
CSS:
body {
--thumbNumber: "5"; // updates on slider input via JavaScript function
}
#slider {
-webkit-appearance: none;
outline: none;
width: 400px;
height: 3px;
background-color: #555;
}
#slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 30px;
height: 30px;
cursor: pointer;
background: #5a5;
}
#slider::-webkit-slider-thumb::before { // doesn't work (refer to above link)
content: var(--thumbNumber);
}
HTML:
<body>
<input type="range" id="slider" value="5" min="1" max="10" />
</body>
JavaScript:
window.onload = function () {
var slider = document.getElementById("slider");
slider.addEventListener("input", function () {
document.body.style.setProperty("--thumbNumber", "'" + this.value + "'");
});
// whenever this element receives input, change the value of --thumbNumber to this element's value
}
I thought I was pretty clever by using CSS variables to avoid the problem of not being able to directly change the properties of a pseudo-element in JavaScript. I learned, however, that this approach cannot work because you cannot use more than one pseudo-element selector in the same selection. (Refer to above link).
2. Dynamically changing the thumb's content property with JavaScript
This method is very similar to the previous one. The only difference is in the CSS. (Spoiler: This method also didn't work)
CSS:
#slider::-webkit-slider-thumb {
content: var(--thumbNumber);
}
The JavaScript is the exact same as before. The idea is that the event listener attached to the slider element listens for input events, and reacts by changing the value of the CSS variable --thumbNumber. This causes the content property of the thumb to update and display the change. This doesn't work, however, because CSS doesn't seem to let you change the content property of the thumb.
3. Using CSS Variables and... images...
This is basically as bad as it gets when it comes to dirty solutions. This is completely unscalable, but it's only a little bit godawful when working with less than 10 numbers on an input range.
For this example, I made 10 png files in photoshop in about 10 minutes. Each file is a 30px by 30px image of a number on a transparent background. Their filenames are 1.png, 2.png, 3.png, and so on until 10.png. Each file is stored in a folder called png. This folder is located in the same folder as the html document.
CSS:
body {
--sliderImage: url("png/5.png");
}
#slider::-webkit-slider-thumb {
background-image: var(--sliderImage);
}
JavaScript:
window.onload = function () {
var slider = document.getElementById("slider");
slider.addEventListener("input", function () {
document.body.style.setProperty("--sliderImage", "url('png/" + this.value + ".png')");
});
}
This example is obviously terrible in so many ways, but it's all I've been able to try that has worked.
Basically, this makes it so that when the slider element receives input, the value of the CSS variable --sliderImage is changed to, for example, url('png/6.png') when the slider thumb is dragged onto the value 6. This causes the background-image property of the thumb to update to a picture that correctly represents the value of the slider.
I am still searching for and trying to come up with an actual, reasonable answer to this problem. It puzzles me how such a seemingly simple task can, in reality, be very layered and difficult. I'll edit my answer as soon as I figure it out.
You cannot display value in the thumb but you can attach a bubble to the thumb which will contain and display the value as you slide. Refer to this detailed article Show slider value.
Here's a demo.
You create a div that will follow the handle.
Then, every time the handle is moved/changed (onslide/onchange), put the value of the handler to the div and also modify the current position of the div the same with the handle so that it will follow the handle.
or maybe, if possible, on onchange method of the slider get the id of the handle and just put
$('#handle').text($(this).val());
I'm just giving you ideas here. I'm sure there will be better answers.
Webkit allows you to target the thumb with css :
.range-consideration::-webkit-slider-thumb::before {
content: "test";
}
That will print test on the thumb. However, using attr(value) doesn't seem to work, so I don't know how to show the value. Maybe you can find a way by playing with css variables and changing it with javascript.
The equivalent for other browsers are ::-moz-range-thumb and ::-ms-thumb.
EDIT : This is really (really !) a dirty way, but it works and it's doable as long as you have a relatively limited range of values.
HTML :
<input class="range-consideration" type="range" min="1" max="10" value="3" onchange="this.setAttribute('value', this.value);" />
CSS :
.range-consideration[value="1"]::-webkit-slider-thumb::before { content: "1"; color: black; }
.range-consideration[value="2"]::-webkit-slider-thumb::before { content: "2"; color: black; }
.range-consideration[value="3"]::-webkit-slider-thumb::before { content: "3"; color: black; }
.range-consideration[value="4"]::-webkit-slider-thumb::before { content: "4"; color: black; }
.range-consideration[value="5"]::-webkit-slider-thumb::before { content: "5"; color: black; }
.range-consideration[value="6"]::-webkit-slider-thumb::before { content: "6"; color: black; }
.range-consideration[value="7"]::-webkit-slider-thumb::before { content: "7"; color: black; }
.range-consideration[value="8"]::-webkit-slider-thumb::before { content: "8"; color: black; }
.range-consideration[value="9"]::-webkit-slider-thumb::before { content: "9"; color: black; }
.range-consideration[value="10"]::-webkit-slider-thumb::before { content: "10"; color: black; }
This "workaround" has lots and lots of lipstick on it... but it is possible with a automated derivation of Sebastien C. answer http://codepen.io/anon/pen/xbQoLj
$(document).ready(function() {
var $daFoo = $('#foo');
var rule = "<style>";
// get mix & max of the slider
for(var i= parseInt($daFoo.attr("min")); i<= parseInt($daFoo.attr("max")); i++)
// for each value possible in this SINGLE!! slider - push a rule
rule += 'input[type=range][data-value="' + i + '"]::-webkit-slider-thumb::after { content: "' + i + '"; }';
$('head').append(rule + "</style>");
$daFoo.on('input', function() { $daFoo.attr('data-value', $daFoo.val()) });
});

Categories