Directing animation to correct place - javascript

In my word game there is a grid with 3 letter words.
The aim of the game is to spell the words by clicking on the corresponding letters on the side.
When an area in the grid is highlighted it indicates to the user the word to spell. The user clicks the letters on the side of the grid and they should move to the highlighted area.
I have recently changed "drop-box" to a div in the following piece of code and now the animation takes the letter to the top corner of the grid before taking it to the correct position.
var row = '<tr>';
var spaceAvailInRow = numLetters;
while (spaceAvailInRow) {
var word = getWordToFitIn(spaceAvailInRow, unusedShuffledWords);
guesses[word] = [];
spaceAvailInRow -= word.length;
for (var k = 0; k < word.length; ++k) {
row += '<td data-letter="' + word[k] + '" data-word="' + word + '"><div class="drop-box"></div></td>';
}
}
row += '</tr>';
tbl.append(row);
}
$(".container").append(tbl);
Can someone tell me why the animation has broke now I have changed this?
Fiddle: http://jsfiddle.net/7Y7A5/27/

The problem is that position() gets an element's position relative to its (offset) parent - in your case, each drop-box div is positioned at 0,0 releative to its containing td. What you need is the containing td's position(), not the drop-box's.
I've changed things around a little, and the fiddle can be seen here: http://jsfiddle.net/mHDkV/1/
I've changed the targetPos variable to refer to the position of the parent td, and applied the occupied class to that td as well. Take a look though the code in the jsFiddle - it should hopefully make sense.

Related

How to increase each successive image in a for loop by 5 pixels

I wrote a script in a section of my HTML code to output an image to my browser 10 times by using a for loop. This works fine, but I also want to write a script in the head element of the markup where I can maybe use a function to first create an HTMLCollection from the image elements, and then loop through all ten images in the collection, adding 5 pixels to the width and height properties of each succeeding image element from left to right when any one of the ten images is clicked.
I've tried to research information on HTMLCollections combined with DOM related properties and other equations, but have been unsuccessful so far.
Script from the body element
<section>
<h2>Growing Pumpkins</h2>
<p id="smashingPumpkins" onclick="growingPumpkins(this)" ></p>
<script>
for (var i = 0; i < 10; i++) {
document.getElementById("smashingPumpkins").innerHTML += "<img src='bandit.png' />";
}
</script>
</section>
Script from the head element:
<script>
function growingPumpkins(img) {
var img = document.images;
for (index in img) {
document.getElementById("smashingPumpkins").innerHTML = img[index].style.width + 1.05;
document.getElementById("smashingPumpkins").innerHTML = img[index].style.height + 1.05;
}
}
</script>
The size of each image should increase by 5 pixels spanning from left to right (apologies for the redundancy) when output to the browser. However, I have been unable to accomplish this, and only see the number 1.05 when I click on one of the images.
What about this?
for (var i = 0; i < 10; i++) {
document.getElementById("smashingPumpkins").innerHTML +=
`<img src='bandit.png' style="width:${base + i*5}px; height:${base + i*5}px "/>`;
}
Of course the 'base' being used in the style attribute is something you set, like 100 or however many pixels you want the height and width of the first one to be
EDIT:
That was just to make each picture bigger than the last, which is not exactly what you asked. But your comment makes me think I should leave it for learning purposes. What you could do have each picture expand on click, and each one expand 10px bigger than the one before it, is this:
Add a class to your images to easy targeting, then target them into an array:
for (var i = 0; i < 10; i++) {
document.getElementById("smashingPumpkins").innerHTML +=
"<img src='bandit.png' class="expandingImg" />";
}
var images = document.querySelectorAll('.expandingImg') // creates an array of your image elements
Now loop through your array of elements and attach an event listener to each one. The event listener will change the style element of that element, based on the element's position in the array. So elements later in the array will expand more than ones earlier in the array. This also eliminates the need for your onclick="growingPumpkins(this)" because the eventlisteners are being added in this immediately invoked functional expression (IIFE):
(function growingPumpkins() {
for (let i = 0; i < images.length; i++) {
images.addEventListener('click', function(){
images[i].style = `style="width:${base + i*5}px; height:${base + i*5}px`
}, false)
})()
I think that should do it. You'll need to add another event listener so that they shrink back down to their original size, like mouseleave or something like that. Hopefully this works and is helpful.

Highlight slices of text with Javascript

Problem
Suppose that in the backend of my Web application I have a generic string of letters:
seq = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
and an array of positions in such a string:
pos = [(0, 2), (4, 8)]
I need to render this sequence in the frontend by splitting it every n characters. Then when a user clicks a button I need to highlight the sequence between two parameters (taken from pos) for which the button refers to.
My solution
I solve this by implementing a Javascript function formatSequence which splits seq every n characters and iterates through the pos array in order to wrap each substring inside a span tag. The result is something like this:
<pre>
<span class="A">AA</span>AA<span class="B">A</span>
<span class="B">AAA</span>AA
AAAAA
</pre>
When the user clicks the button referring to the class A I simply change the CSS background rule for class A.
It works :) But the function formatSequence is way too complicated imho. It was a pain dealing with multiple lines. I prefer not posting the code since I am looking for other approaches not changing the code of such function.
A better solution?
I think that a (better?) solution would be to implement a function that given two parameters start and end it dynamically highlights the text between them. But it appears to be even more complicated than the previous one (remember that the sequence must be split every n characters and thus the highlight must be multilines).
Any suggestions? Better approach to solve this?
One simple solution would be just to print the full seq multiple times into the HTML and hide every row you don't need at the time. When a user clicks on a button, another row would be displayed (and the first one would be hidden).
HTML:
<div class="rows"></div>
<div class="buttons"></div>
JavaScript (depending on jQuery):
(function generateRowsAndButtons() {
var sequence = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
var position = [ [0,2], [4,8] ];
var $rows = $('.rows');
var $buttons = $('.buttons');
for(var i = 0; i < position.length; i++) {
if(position[i].length !== 2 || position[i][0] > position[i][1]) {
console.log("every position array needs exactly two values with the second larger than the first one");
continue;
}
// the index is used for mapping the button the highlight position
var row = '<div class="row" data-index="' + i + '" style="display: none;">';
// you should add some checks here, if position larger then the length of the string to avoid some misbehaviors. this is of course only necessary if you aren't validating the values on another place.
row += sequence.substring(0, position[i][0]);
row += '<span class="highlighted">';
row += sequence.substring(position[i][0], position[i][1]);
row += '</span>';
row += sequence.substring(position[i][1]);
row += '</div>';
var $row = $(row);
$rows.append($row);
// a button needs the index to find the link the highlighted value
var $button = $('<button data-index="' + i + '">' + position[i] + '</button>');
$buttons.append($button);
}
$buttons.find('button').click(function() {
var index = $(this).data('index');
// hide every row, except the one with the correct index
$rows.find('.row').hide().filter('[data-index="' + index + '"]').show();
});
})();
CSS:
.row .highlighted {
background: yellow;
}
Here is a jsFiddle: https://jsfiddle.net/y8uoou1L/2

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)

Changing text color in Greasemonkey

I want to write a Greasemonkey script that will change the color of the text on any page while leaving the structure as it is. I would like to change the first 10 visible characters to be red, the next 10 to be blue, the next to be red again and so on.
I see two possible ways of going about this:
iterating through every element on the page, checking if it has text that is displayed and changing the text color. I guess this can be done by getting all elements using document.getElementsByTagName('html')[0].innerHTML and then calling elements[i].textContent to get the text but I do not know how to determine if the text is visible or not. This will return the text inside <script> elements and adding color attributes to those elements will break the page.
selecting the text on the page with something like window.getSelection().addRange(WholePage) but then I don't know of any way of changing the text color.
If you think of any other method please feel free to suggest it.
Try this (use jQuery).
$('p, li').each(function(){
var length = $(this).text().length;
var newStr = "";
for (var i = 0; i < length; i+=20) {
newStr += '<span style="color:red">' + $(this).text().substring(i, i + 10) + '</span>';
newStr += '<span style="color:blue">' + $(this).text().substring(i + 10, i + 20) + '</span>';
}
$(this).html(newStr);
});

Can I select an nth css column?

I have a div with 4 css columns and I'd like to select the 3rd and 4th column to make the text slightly darker because I don't have a good contrast between the text and the background-image. Is this possible? I can accept any css or js solution.
Here's the demo.
--EDIT--
It seems that it's not possible to find a selector for pseudo blocks (if I may say) however I still need to figure out a way of creating responsive blocks (like columns) that will split the text equally (in width) whenever the browser is resized.
As far as I know you won't be able to apply styles to the columns.
What you can try however is to use a gradient as a background to make columns 3 and 4 another color.
#columns {
background: -webkit-linear-gradient(left, rgba(0,0,0,0) 50%, blue 50%);
/*... appropriate css for other browser engines*/
}
updated jsFiddle
updated with all browser support gradient
-- EDIT --
Since the intention was actually to change the text color and not the background for the third and fourth column some additional thoughts.
For now it doesn't seem possible to apply styles to single columns inside a container. One possible workaround to change the text color in specific columns is to put every word inside a span. Then to use JavaScript to iterate over the words and determine where a new column starts. Assigning the first element in the third column a new class would make it possible to style this and the following siblings with a different text color.
Because the container is part of a responsive layout and could change in size, the script would have to be re-run on the resize event to account for changing column widths.
The purpose of the code example is to outline how to implement such a solution and should be improved for use in an actual application (e.g. the spans are being re-created every time styleCols is run, lots of console output...).
JavaScript
function styleCols() {
// get #columns
var columns = document.getElementById('columns');
// split the text into words
var words = columns.innerText.split(' ');
// remove the text from #columns
columns.innerText = '';
// readd the text to #columns with one span per word
var spans = []
for (var i=0;i<words.length;i++) {
var span = document.createElement('span');
span.innerText = words[i] + ' ';
spans.push(span);
columns.appendChild(span);
}
// offset of the previous word
var prev = null;
// offset of the column
var colStart = null;
// number of the column
var colCount = 0;
// first element with a specific offset
var firsts = [];
// loop through the spans
for (var i=0;i<spans.length;i++) {
var first = false;
var oL = spans[i].offsetLeft;
console.info(spans[i].innerText, oL);
// test if this is the first span with this offset
if (firsts[oL] === undefined) {
console.info('-- first');
// add span to firsts
firsts[oL] = spans[i];
first = true;
}
// if the offset is smaller or equal to the previous offset this
// is a new line
// if the offset is also greater than the column offset we are in
// (the second row of) a new column
if ((prev === null || oL <= prev) && (colStart === null || oL > colStart)) {
console.info('-- col++', colCount + 1);
// update the column offset
colStart = oL;
// raise the column count
colCount++;
}
// if we have reached the third column
if (colCount == 3) {
// add our new class to the first span with the column offset
// (this is the first span in the current column
firsts[oL].classList.add('first-in-col3');
return;
}
// update prev to reflect the current offset
prev = oL;
}
}
styleCols();
addEventListener('resize', styleCols, false);
CSS
.first-in-col3, .first-in-col3~span {
color: red;
}
jsFiddle
For now i dont think you can do it, here https://bugzilla.mozilla.org/show_bug.cgi?id=371323 is an open bug/request for a feature, you can vote for it. Till then you can consider using tables.
P.S.
Give Up and Use Tables just for the sake of humor :)
The only solution i could think would be a background with your desired color for middle column, customize it for size and position so it goes behind your middle columns and make background-clip:text. Unfortunately it is not supported very well.
You can find more explenations here.

Categories

Resources