How can I add accumulative Margin on button click? - javascript

I am trying to add a text slider, where basically a very long text box extends out of the view, and when a button is pressed margin is added so more text can be read. I do not know how to make a button that adds margin without exponentially increasing it each time.
<div class='long'>
<div class='container'>
<button type="button" onclick="display()">Add left margin</button>
<p id="myID">This is demo text.</p>
<script>
function display() {
var e = document.getElementById("myID").style.marginLeft += 1 document.getElementById("myID").style.marginLeft;
}
</script>
After a few clicks, this starts to increase the margin insane amounts. I believe it is because the 1 is not 1px, but some other unit which makes it add up insanely quick. SO I want to have a button that adds 1px of margin per click, so it smoothly adds margin instead of a big unuseable jump.
My hypothesis is that you need to get the margin value, store it, then add it to a string that has 'px' at the end then update the margin with it?

You are correct with your thoughts. Store the margin value in a variable outside the function, and increase it by one each time.
The style.marginLeft returns 1px and not 1 which means you cannot increment to it.
var margin = 1;
function display() {
document.getElementById("myID").style.marginLeft = ++margin + "px";
}
<div class='long'>
<div class='container'>
<button type="button" onclick="display()">Add left margin</button>
<p id="myID">This is demo text.</p>
</div>
</div>

The parseInt and parseFloat functions will return numeric value for marginLeft (i.e. without the trailing 'px' that is causing your issue).
function display() {
let el = document.getElementById("myID");
el.style.marginLeft = `${parseInt(el.style.marginLeft)+1}px`;
}

The problem is that happen string concatenation when you use the + operator
You have to convert the value returned from element.style.marginLeft to a number. You can call the Number() function or use the + operator immediately attached how in my solution. Another problem could be that the value returned is like that 1px for example and to increment it you have to parse it, I used split function
function display() {
let currentMarginValue = +document.getElementById("myID").style.marginLeft.split('px')[0];
console.log(currentMarginValue)
var e = document.getElementById("myID").style.marginLeft = currentMarginValue + 1 + "px" ;
}

you may need one counter that will increase in every click. please refer below code
<script>
let count = 0;
function display() {
document.getElementById("myID").style.marginLeft = ++count+"px";
}
</script>

Related

I'm using html and have two buttons. I want these buttons to have different typeWriter() function outputs

Please keep in mind I am a beginner. It would be very helpful if you could avoid complicated terminology. The issue is that I cant give buttons specific functions. I tried using id and name. I thought it would have been easier if I had the typewriter effect go after pre-existing text but it didn't work and both inputs (buttons) use the second command. All I really need is a way to link the buttons to a specific function.
I originally didn't plan on using the method of having the 1 and 2, so if you have a method which is only typewriter that would work for me as well.
var i = 0
var x = document.getElementById("button1")
var txt = 'button 1';
var speed = 80;
function typeWriter() {
if (i < txt.length) {
document.getElementById("button1").innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, speed);
}
}[]
var i = 0
var x = document.getElementById("button2")
var txt = 'button 2';
var speed = 80;
function typeWriter() {
if (i < txt.length) {
document.getElementById("button2").innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, speed);
}
}[]
<div id="container">
<h1>name</h1>
<button style="font-size:25px" onclick="typeWriter()" id="#button1">button number
1</button>
<button style="font-size:25px" onclick="typeWriter()" id="#button2">Button number 2</button>
<h1 id="button1"> l</h1>
<h1 id="button2"> 2 </h1>
<h3 style="color:white;"></h3>
Instead of creating a function for each button, you can reuse the same function and pass it the text and target element, like so:
const speed = 80;
function typeWriter(elementId, text, i = 0) {
const element = document.getElementById(elementId)
if (i < text.length) {
element.innerHTML += text.charAt(i);
setTimeout(() => typeWriter(elementId, text, ++i), speed);
}
}
<div id="container">
<h1>name</h1>
<button style="font-size:25px" onclick="typeWriter('button1', 'some text for button 1')" id="#button1">button number
1</button>
<button style="font-size:25px" onclick="typeWriter('button2', 'some text for button 2')" id="#button2">Button number 2</button>
<h1 id="button1">l </h1>
<h1 id="button2">2 </h1>
<h3 style="color:white;"></h3>
OK so I think you want certain text, associated with a certain button somehow, to be "typed out" in the h1 tags like a typewriter. Let me know if its something else. If so, you need to add parameters to the function, instead of defining it twice, like the following:
function doTheTyping(id, txt) {
var speed = 80;
var i = 0
var el = document.getElementById(id);
function typeWriter() {
if (i < txt.length) {
el.innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, speed);
}
}
if(el) typeWriter();
}
$button1.onclick = function() {
doTheTyping("button1", "button 1");
};
$button2.onclick = function() {
doTheTyping("button2", "button 2");
};
<div id="container">
<h1>name</h1>
<button style="font-size:25px" id="$button1">button number
1</button>
<button style="font-size:25px" id="$button2">Button number 2</button>
<h1 id="button1"> l </h1>
<h1 id="button2"> 2 </h1>
<h3 style="color:white;"></h3>
</div>
a parameter is like a variable that changes for each function. Notice now there is a parent function, doTheTyping, which has all of the variables (especially i) reset, and within that function itself does the setTimeout start (and increment i), but before i was being incremented by one of the buttons, and by the time it was done, no others would work.
The problem before was that the same variables were being defined twice. If a function or variable has the same name as another one, it has to be renamed, if a different result is expected for it, or it can be reduced to one function that can have different behavior based on the parameters.
Another thing is I noticed the buttons themselves were assigned IDs, but those were never used, so in general the onclick events were moved into a JavaScript tag, to give some more flexibility, instead of having everything inside of the quotes on the HTML page itself
Let me know if you have any questions
You can use a common parametric function called by each button.
Something like that:
function toButton1() {
typeWriter('button 1', 'button 1', 0, 80);
}
function toButton2() {
typeWriter('button 2', 'button 2', 0, 80);
}
function typeWriter(id, txt, i, speed) {
if (i < txt.length) {
document.getElementById(id).innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter(id, txt, i, speed), speed);
}
}
<button style="font-size:25px" onclick="toButton1()">button number 1</button>
<button style="font-size:25px" onclick="toButton2()">Button number 2</button>
<h1 id="button1"> l</h1>
<h1 id="button2"> 2 </h1>
Live Demo: https://stackblitz.com/edit/js-ge9sm8
Since you're new to programming I'll do my best to answer this as clearly as I can. Below you will find a solution to your question, but note that there are many other possible (and probably better) solutions you could come up with.
Most of the changes to the javascript code are explained in the comments that correspond to the particular lines, but there are a few things I want to explain first:
I changed your uses of var to either let or const. These are ways to declare variables introduced with one of the more recent versions of JavaScript, and essentially they are safer ways of declaring variables than var. The main difference between let and const is that varibales defined with the const keyword are constant, meaning they cannot be assigned a different value later on in the code. This is fine for the txt, speed, and button variables as you don't need to change them later on, but note that for i, which you're changing within the main writing function I used let, telling the engine that this is a variable that is expected to change. However, this is mostly a thing of personal preference, and it's ok to use var instead.
I'm not sure if you're familiar with parameters, but essentially they allow you to pass values into a function to change how that function acts. They're defined in the parenthesis ("(...)") after the function name and can then be used anywhere within the body of that function. In this example I'm using them to pass the number of the button, so that I can use the number to adapt the text and get the appropriate element. Notice that in the HTML code, I pass the number 1 and 2 to the function in the onclick handle, to do just that
/**
* Run a typewriter effect for the element associated with the index parameter passed
* into the function.
*
* #param {Number} index The index of the element to associate the animation with
*/
function typeWriter(index) {
let i = 0
// I use the ` symbol here as it allows me to insert a varibale into the string.
// When I then surround [index] with ${} it tells the engine to grab the current
// value of the [index] variable and replace the "${index}" definition with its
// current value. That way this line generates a different text based on what
// index is passed in as a parameter
const txt = `button ${index}`;
const speed = 80;
// The logic for using the backticks (`) and the ${} here is the same as in the
// "const txt = " line above. Inserting the current value of index into the string
// means that this line fetches a different element based on what is provided as the
// index parameter of the function.
const button = document.getElementById(`button${index}`)
// Since the above line gets the element dynamically based on the input it is possible
// that the input will not correspond to an element. If that's the case then button
// will be [null] and we want to just bail out of this whole function and not do
// anything else
if (!button) { return }
// Here I reset the innerHTML of the element. It's up to you if you want to do this
// or just keep adding to the text already in the element, but I found this visually
// more pleasant.
button.innerHTML = ""
// Here I just wrapped the code you had in the [typewriter] function before inside a
// new nested function so that you can still call it from the [setTimeout] call
function writeLetter() {
// As above we first ensure that [button] is a valid element before we do
// anything else.
if (!button) { return };
if (i < txt.length) {
button.innerHTML += txt.charAt(i);
i++;
setTimeout(writeLetter, speed);
}
}
// Here I just need to call the above defined function once to kick off the
// writing effect.
writeLetter()
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Typewriter</title>
</head>
<body>
<div id="container">
<h1>Typewriter</h1>
<button style="font-size:25px" onclick="typeWriter(1)" id="#button1">button number 1</button>
<button style="font-size:25px" onclick="typeWriter(2)" id="#button2">Button number 2</button>
<h1 id="button1">1</h1>
<h1 id="button2">2</h1>
</div>
I hope that was helpful. Of course if you have any questions feel free to comment on my post and I or someone else will do our best to answer. Good luck in your coding career :)
-- Jakub

Javascript onclick increment a number as text

I have a problem with Javascript: I'm trying to increment a number which is a string, so I need to parse it, increment the value, and assign that number to the value in the field. But I don't understand why this code doesn't work properly:
<button type="button" onclick="dec()" name="less" style="background-color: orange;border:none;">-</button>
<script>
function dec() {
var x = parseInt(document.getElementById("num").value, 10);
x--;
document.getElementById("num").value = x;
}
While the number is in a div like this:
<div id="num" style="display:inline;">0</div>
Where is the error?
For one hand, if you want to increment the value you need to do x++ instead of x--. And after that, I think you will fix it with:
document.getElementById("num").innerHTML= x;
Because a div does not have a value property.
Just use
document.getElementById("num").innerHTML
instead

Prevent overlapping while positioning element at height of another

Inside a long text document there are some "special words" to which I want to display notes/annotations on the left. Each note should be as close as possible to the level of the word it is refering to.
The HTML for this is organised in a table. Each paragraph is one table row, consisting on annotations in the left and main text in the right table column. the notes/annotations go to the left. However, unfortunately, there are also some other elements/text nodes in there.
<table>
<tr>
<td class"comments">
<span id="dog" class="note">Note for dog</span>
<span id="cat" class="note">Note for cat</span>
<span id="horse" class="note">Note for horse</span>
Somethin else than a note.
</td>
<td>[Text...]
<span id="dog_anchor" class="reference">Dog</span>
<span id="cat_anchor" class="reference">Cat</span>
<span id="horse_anchor" class="reference">Horse</span>
[Text...]
</td>
</tr>
</table>
It's easy to change the "note"-spans to absolute and positioned them on the level of their reference:
$('span[class*="note"]').each(function (index, value) {
var my_id = $(this).attr('id');
var element_ref = document.getElementById(my_id + "_anchor"); // get reference element
var pos_of_ref = element_ref.offsetTop; // get position of reference element
$(this).css('top', pos_of_ref); // set own position to position of reference element
});
However, life is not so simple here. Since there could be a lot of reference words in one line (while on other there are none of them) I need a rather sophisticated way to distribute the notes so that they are as close as possible to their references without destroying anything in the layout (e.g. being placed outside of the table cell or overlapping with other elements).
Furthermore, the height of the table cells could not be changed. Elements which are not notes must not be moved. (Note elements are always in the order they appear in the main text. That's not the problem.)
So, I need an algorithm like this:
Take all notes in a table cell.
Analyse blank space in that table cell: Which areas are blank, which are blocked?
Distribute the notes in the table cell so that each note is as close as possible to its reference word without any element colliding with any other item in the table cell.
Is there any fast and elegant way to do this without having to write hundreds of lines of code?
Here is a JSfiddle: https://jsfiddle.net/5vLsrLa7/7/
[Update on suggested solutions]
Simply setting the position of the side notes to relative or just moving notes down won't work, because in this case, the side notes will just go downwards relative to their desired position which results in side notes way to far from their reference words. After all, for a neat solution I need to side notes spread in both directions: up and down.
[Update]
The expected result would be something like this:
As you see, it's never possible to place all the notes at the height of their reference. However, the free space is used to position them as close as possible, moving them up and down.
I changed move() function as follows:
function move(){
var prev_offset = 0;
$('span.note').each(function (index, value){
var my_id = $(this).attr('id');
var element_ref = document.getElementById(my_id + "_anchor"); // get reference element
var pos_of_ref = element_ref.offsetTop; // get position of reference element
if (prev_offset >= pos_of_ref){
pos_of_ref = prev_offset + 30;
}
$(this).css('top', pos_of_ref); // set own position to position of reference element
prev_offset = pos_of_ref;
});
}
I'm assuming that your element's notes will be in the correct order always
I made some changes to your javascript:
function move()
{
var arrayTops = [];
$('span[class*="note"]').each(function (index, value)
{
var my_id = $(this).attr('id');
var element_ref = document.getElementById(my_id + "_anchor"); // get reference element
var pos_of_ref = element_ref.offsetTop; // get position of reference element
pos_of_ref = getCorrectTopPosition(arrayTops,pos_of_ref);
$(this).css('top', pos_of_ref); // set own position to position of reference element
arrayTops.push(pos_of_ref);
});
}
function getCorrectTopPosition(arrayTops, newOffsetTop)
{
var notesHeight = 18;
var marginBetweenNotes = 3;
var noteheightWithMargin = notesHeight + marginBetweenNotes;
var lastTop = arrayTops[arrayTops.length-1];
if((lastTop + noteheightWithMargin) >= newOffsetTop)
return lastTop + noteheightWithMargin;
return newOffsetTop;
}
Thanks for all the answers and comments. I was finally able to figure out at least a partical solution which works for me.
First of all, I was able to restructure my HTML, so that now the "non note" elements in the left td are all wrapped in one div which is now the very first element in the td. So, now there is nothing between notes, maybe something before them.
The idea of my solution is not to give the notes a new position but to set a new margin-top to each of them. The maximum amount of margin-top values to be added within a table cell is calculated before (called "roaming space"), being the space below the last note in a table cell. Thus, the table layout is not destroyed.
function move_notes() {
$('tr').each(function (index, value) {
var current_tr = $(this);
var last_app_element_in_tr = $(this).find('span[class*="note"]').last();
if ($(last_app_element_in_tr).length) /* Only preceed if there is at least one note in the table row */ {
var tr_height = $(this).height();
var tr_offset = $(this).offset().top;
var bottom_of_tr = tr_offset + tr_height;
var bottom_of_last_app_el = $(last_app_element_in_tr).offset().top + $(last_app_element_in_tr).height();
var roaming_space = bottom_of_tr - bottom_of_last_app_el; // Calculate the amount of pixels which are "free": The space below the very last note element
$(this).find('span[class*="note"]').each(function (index, value) {
var my_id = $(this).attr('id');
var element_ref = $(current_tr).find("#" + my_id + "_anchor");
var pos_of_ref = $(element_ref).offset().top;
var new_margin_top;
/* Calculate the new margin top: The note should be at the same level as the reference element.
When loading, in most cases the notes are placed too high. So, the margin top of the note should equal
the amount of pixels which the note is "too high". So we subtract the height and the offset of the element
before the current note from the offset of the reference. */
var previous_note = $(this).prev();
// not just notes, but every element in the td in general
if (! $(previous_note).length) // If there is no previous_note, than take the table cell
{
closest_td = $(this).closest("td");
new_margin_top = pos_of_ref - $(closest_td).offset().top;
} else {
new_margin_top = pos_of_ref - $(previous_note).offset().top - $(previous_note).height();
}
var difference_to_previous = $(this).css('marginTop').replace(/[^-\d\.]/g, '') - new_margin_top; // Calculate the difference between the old and the new margin top
if (new_margin_top > 0 && Math.abs(difference_to_previous) > 2) // Only move, if the new margin is greater than zero (no negative margins!) if the difference is greater than 2px (thus preventing ugly "micro moving".
{
var new_roaming_space = roaming_space - difference_to_previous;
if (new_roaming_space > 0) /* if there is still room to move */ {
var margin_top_ready = new_margin_top + "px";
$(this).css('margin-top', margin_top_ready);
roaming_space = new_roaming_space;
} else /* If there is no more space to move: */ {
var margin_top_ready = roaming_space + "px"; // take the rest of the "roaming space" left as margin top
$(this).css('margin-top', margin_top_ready);
return false; // Stop the execution because there is nothing left to do.
}
}
});
}
});
}
window.onload = function () {
move_notes();
};
$(window).resize(function () {
move_notes();
});
As you will notice, one of my main concerns is still not addressed: Notes are only moved down, never up. Because of various problems with my real world webpage I didn't implement that yet. However, an algorith could be something like: If the new margin top is greater than the height of the current note and the difference between the offet of the current note anchor and the following note anchor is less than the height of the current note, than subtract the height of the current note from the new margin.
Still, two problems remain:
If the window is maximized or quickly resized from a rather thin width to a greater width, the adjustment of the note positions won't work. I don't know why.
The performance could be better. As a user, you can see the notes jump down. (Because of strange and unpredictable behaviour in Firefox, I had to move the event handler from document.ready to window.onload)

JavaScript: why doesn't this simple animation test work?

<p id="test">Test</p>
<button onclick="move(getElementById(" test "))">click me</button>
function move(elem) {
var lft = 0;
function anim() {
lft++
elem.style.left = lft + "px";
if (lft == 100) {clearInterval(myVal);}
}
var myVal = setInterval(function(){anim()}, 500);
}
I'm just trying to get the "Test" text to move across the screen. This doesn't work.
http://jsfiddle.net/qfovLayv/2/
The left property only applies to positioned elements.
A positioned element is on where the value of the position property is something other than static.
static is the default value and you haven't changed it.
Set position to fixed, relative, or absolute depending on what you actually want.
Additionally, if you are using a " to mark the start an end of an attribute value, then the next " will end the attribute value.
onclick="move(getElementById("
If you want to use a " character as data in such a value, you must use a character reference: "
You could use ' instead.
Additionally, your ID doesn't have any spaces in it, so you shouldn't have any spaces in the string you pass to getElementById.

javascript: for each item in array: create div, move div and remove div after timeperiod

I have a list of names; and I want to display these names one by one. The name should apear in the top of a container, subsequently move to the bottom, and then disapear. In the mean time, the next name should have appeared in the top of the container, started moving,..., etc.
I'm not that for yet:
I still need to create the code which makes the divs move; I'll do that later and it is maybe easier to create a separate function for that.
I realize very well the way in which I'm trying to achieve this is very likely too cumbersome, but I'm just a beginner...
So for I made:
function myNamesFunction() {} {
var y = 0;
for (y = 0; y < document.getElementById("namelistID").getElementsByTagName('div').length;y++)
{
var vinden2 = document.getElementById("namelistID").getElementsByTagName('div')[y];
{
var div = document.createElement("div");
div.innerHTML = (document.getElementById("namelistID").getElementsByTagName('div')[y].innerHTML);
setTimeout(function () {
document.getElementById("namecontentbox").appendChild(div)[y];
}, 0000);
setTimeout(function () {
document.getElementById("namecontentbox").removeChild(div)[y];
}, 2000);
}
}
}
The Html code is:
<div id="namelistID" style="display: none;">
<div class="nameofperson">Person1</div>
<div class="nameofperson">Person2</div>
<div class="nameofperson">Person3</div>
<div class="nameofperson">Person4</div>
</div>
<div id="namecontentbox"></div>
The problem is now that since I added the "settimeout", only the last item from the array seems to be created and removed. How can I start with the first, and then let each item appear and disappear?
(Any other advice on how to continue (or restart...) is very welcome too)
That's a classical problem, due to y's value changing by the time the function you give to setTimeout is called.
The classical solution is to protect y with a closure in your loop :
for (y = 0; y < document.getElementById("namelistID").getElementsByTagName('div').length;y++) {
(function(y){
// use y
})(y);
}
This function is the scope of the new y variable and, as it's immediately executed, it keeps protected the value y had at this time.
If you want the timeout to be executed one after the other, and not all at the same time, increment the time :
setTimeout(function () {
document.getElementById("namecontentbox").removeChild(div)[y];
}, 2000*(y+1));

Categories

Resources