views:

182

answers:

4

Update: I left the following javascript code in to show how the problem developed over time, but it turns out now this is NOT relevant as javascript is not the issue. Please take a look at the html/css code below.

For an overview page with a large menu, I implemented the following function (most of which I stole from here):

function isScrolledIntoView(elem)
{
    var docViewTop = $(window).scrollTop();
    var docViewBottom = docViewTop + $(window).height();

    var elemTop = $(elem).offset().top;
    var elemBottom = elemTop + $(elem).height();

    alert(elemBottom);

    return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom));
}

$(document).ready(function(){
 var overview = $('#overview');
 var active = $('#active-item');
 if (!isScrolledIntoView(active))
 {
  $('#overview li.active-parent').each(function(index, value){
   if (!isScrolledIntoView(active)) overview.scrollTo(value);
  });
 }
 if (!isScrolledIntoView(active)) overview.scrollTo(active);
});

The idea is that after every page load, the containing the menu is scrolled into a position where the current #active-item is visible. Preferably by scrolling to its first parent (the menu items are in a tree) otherwise to the item itself.

Now this works fine in Firefox and Chrome (and none of the apple people have complained to me), but Opera does a really strange thing: it scrolls down to the correct element, then pauses very briefly before scrolling all the way up again

Does anybody have any idea

  1. What's going on, and
  2. How can I stop it?

Thanks,


Update: I'm testing with version 10.63 on linux (Fedora)


Update: it appears I was searching in the wrong direction entirely. The issue appears to be a css thing, and can be replicated with the following code:

<html>

<head>

<title>Opera scroll test</title>

  <style>
    .main:after
    {
      content: 'abc';
    }

    :focus
    {
      padding: 0px;
    }

    #overview
    {
      display: block;
      float: left;
      width: 219px;
      height: 500px;
      overflow: auto;

    }
  </style>

</head>

<body>

  <div id="main" class="main">

    <div id="overview">
      <ul>
        <?
        for($i = 1; $i < 100; $i++)
          echo '<li>'.$i.'</li>';
        ?>

      </ul>
    </div>
    <div>

      <p>123</p>

    </div>

  </div>

</body>
</html>

Now if you scroll down on the navigation pane, and the move your mouse to the right (over the content pane) the scroll of the navigation pane is reset.

Sorry for wasting everyone's time with a javascript hunt :(

If there are any css gurus out there who know how to fix it, or who can simply explain what's going on I'd be very grateful.


Update: tested the above code in windows on opera 10.63. The same strange behaviour occurs.


Update: have reported this to Opera as a bug. Wonder what will happen...

+2  A: 

Sir,

I have an idea of whats going on, however I tried your code and so far I cant seem to replicate the problem. Chrome and Opera behave the same.

This is the HTML I tried with:

<div id="overview" style="height: 300px;overflow-y: scroll;">
 <ul>   
  <li class="item" style="height: 400px;">item 1</li>
  <li class="item" style="height: 400px;">item 2 </li>
  <li class="active-parent" style="height: 400px;">active-parent<br/><br/>
    <ul>
      <li id="active-item" style="height: 300px;">active-item</li>
    </ul>
  </li>
 </ul>
</div>

As to why it doesnt work in Opera: which version of Opera are you using? I tried 10.63 (the latest) and its fine on that one.

Since I cant reproduce the issue I'm going to take a blind shot at it:

Try introducing a slight delay so this code gets executed after everything else in the queue:

$(document).ready(function() {
  window.setTimeout(function() {
    // Your code goes here
  }, 500);
});

LATER ADDITION:

In response to your statement: "I really would like to understand what's going on here though. Why a delay? And would the delay need to be longer on slower computers?":

If setting a delay fixes your problem then the reason is clear, there is some other piece of code interfering with the "scroll to top" functionality that was executing after it. By setting a delay of 500ms you are making sure your code runs after every other piece of Javascript that executes when a document loads, this could could either be something you explicitly added to the jQuery function queue when the document loads (using $(document).ready(function(){), or implicitly added by Opera in the form of a "Widget" (do you have any translation tools, google toolbars etc, installed?).

As to whether the delay needs to be longer for slower computers, I personally dont think so, the key is that the delay forces your code to execute AFTER EVERY OTHER SCRIPT triggered by the document.onload event. Since my suspicion is that whatever is intefering executes in direct succession after the document loads you don't really need a very long delay, even 50ms might do it, the key is that the delay forces your code to go at the back of the queue.

Hope this helps make things more clear.

Steven de Salas
Thanks Steven, I'm using version 10.63 too
Michael Clerx
+1  A: 

As Steven de Salas mentioned, adding a delay helps -> $(window).scrollTop() returns 0 if not delayed!!

I played around a bit and came up with this example:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head>
<title>Scrolling Test</title>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js" type="text/javascript"></script>
<style type="text/css">
.parent { margin-bottom:100px; border:1px solid red; }
</style>

<script type="text/javascript">

(function ($){
    $.fn.extend({
        viewport : function(){
            if (this.length > 0) {

                var element = $(this.get(0)),
                pad = $.fn.viewport.PADDING,
                vp = {
                    height : element.height() + 2 * pad,
                    top : element.offset().top - pad,
                    docHeight : $(window).height(),
                    docTop : $(window).scrollTop(),
                    completelyInViewport : false
                };
                vp.bottom = vp.top + vp.height + pad;
                vp.docBottom = vp.docTop + vp.docHeight;
                vp.fitsInViewport = vp.height <= vp.docHeight;

                if (vp.top > vp.docTop && vp.bottom < vp.docBottom) {
                    vp.completelyInViewport = true;
                }

                return vp;
            }

            return null;
        }
    });

    $.fn.extend($.fn.viewport, {
        PADDING: 10, // ADJUST TO YOUR NEEDS
        LARGE_PARENT_BEHAVIOR: "bottom" // if parent list is bigger than viewport and 
                                        // the active item is not in the first page of 
                                        // the parents list, where should it be shown
                                        // possible: "bottom", "middle" or "top"
    });

    $.extend({
        ensureViewport: function(element, parent) {
            var e_vp = element.viewport(),
            p_vp = parent.viewport();

            if (null == e_vp || null == p_vp) {
                return;
            }

            if (!p_vp.completelyInViewport) {
                if (p_vp.fitsInViewport || e_vp.bottom - p_vp.top <= e_vp.docHeight) {
                    doScroll(p_vp.top);
                } else {
                    switch($.fn.viewport.LARGE_PARENT_BEHAVIOR) {
                        case "top":
                            doScroll(e_vp.top);
                            break;
                        case "middle":
                            doScroll(e_vp.top - (e_vp.docHeight - e_vp.height)/2);
                            break;
                        case "bottom":
                        default:
                            doScroll(e_vp.bottom - e_vp.docHeight);
                            break;
                    }
                }
            }

            function doScroll(y){
                window.scrollTo(0, y);
                // you could implement instead some sort of smooth scroling mechanism here
                // e.g. http://github.com/kswedberg/jquery-smooth-scroll
            }
        }
    });

    $(function(){
        window.setTimeout(function(){
            var item = $("li.active-item");
            if (item.size() > 0) {
                $.ensureViewport(item, item.closest("li.parent"));
            }
        }, 0);
    });
})(jQuery);

</script> 

</head>
<body>

<div id="overview">
 <ul>
  <li class="parent">
   parent 1
   <ul class="item"><li>item 1</li><li>item 2</li></ul>
  </li>
  <li class="parent">
   parent 2
   <ul class="item"><li>item 1</li><li>item 2</li></ul>
  </li>
  <li class="parent">
   parent 3
   <ul class="item"><li>item 1</li><li>item 2</li></ul>
  </li>
  <li class="parent">
   parent 4
   <ul class="item"><li>item 1</li><li class="active-item">item 2</li></ul>
  </li>
  <li class="parent">
   parent 5
   <ul class="item"><li>item 1</li><li>item 2</li></ul>
  </li>
  <li class="parent">
   parent 6
   <ul class="item"><li>item 1</li><li>item 2</li></ul>
  </li>
  <li class="parent">
   parent 7
   <ul class="item"><li>item 1</li><li>item 2</li></ul>
  </li>
 </ul>
</div>

</body>
</html>

Tested it on Opera 10.63 and it's working there too.

Cheers

roberkules
Thanks for the answer! I really would like to understand what's going on here though. Why a delay? And would the delay need to be longer on slower computers?
Michael Clerx