最新消息:Welcome to the puzzle paradise for programmers! Here, a well-designed puzzle awaits you. From code logic puzzles to algorithmic challenges, each level is closely centered on the programmer's expertise and skills. Whether you're a novice programmer or an experienced tech guru, you'll find your own challenges on this site. In the process of solving puzzles, you can not only exercise your thinking skills, but also deepen your understanding and application of programming knowledge. Come to start this puzzle journey full of wisdom and challenges, with many programmers to compete with each other and show your programming wisdom! Translated with DeepL.com (free version)

javascript - Prevent overlapping while positioning element at height of another - Stack Overflow

matteradmin7PV0评论

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"ments">
            <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: /

[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.

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"ments">
            <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/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.

Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Nov 25, 2015 at 12:12 ciscis 1,39118 silver badges61 bronze badges 11
  • Something like this? – Guruprasad J Rao Commented Nov 25, 2015 at 12:51
  • Have you considered using a tooltip or modal window for your notes? It may make your layout a little cleaner and improve your user experience. – apaul Commented Dec 3, 2015 at 22:39
  • Thanks for that hint! However, I cannot do that, since modal windows are alread used within the text for other kinds of notes. While these "other" notes are less important and could be displayed only on clicking, those with which I'm struggling now are so important that they have to be present all the time. – cis Commented Dec 4, 2015 at 8:58
  • As you said, this is not trivial, which kind of also answers the question by saying, Nope, no fast and elegant way to solve without some hard work and coding. Bottom line is, you need to provide some drawings on how the distribution should occur based on the empty space together with non note texts and their precedence. When we have that we will be able to give a better answer on if and how "fast and elegant it will be". – Asons Commented Dec 6, 2015 at 22:32
  • @cis I have a solution that will allow you to optimize space so you can have more text on the left side as well as a way to bring the user's focus to the references and the notes. Unfortunately you said: Design change is not an option. The notes have to be always visible whatever happens..... – zer00ne Commented Dec 7, 2015 at 2:27
 |  Show 6 more ments

3 Answers 3

Reset to default 1

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 ments. 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)
Post a comment

comment list (0)

  1. No comments so far