diff options
Diffstat (limited to 'static/tests/scrolling.html')
| -rw-r--r-- | static/tests/scrolling.html | 437 |
1 files changed, 394 insertions, 43 deletions
diff --git a/static/tests/scrolling.html b/static/tests/scrolling.html index 00e7b89..bd0a08f 100644 --- a/static/tests/scrolling.html +++ b/static/tests/scrolling.html @@ -1,8 +1,35 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" +"http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script> <script> +/* scrolling: + +focus is either on a specific message or at the end + +when to recalculate focus? +* user scrolls +* we force a scroll to end + +when to adjust scroll to focused message? +* page loaded +* window resized +* messages added +* messages deleted +* images loaded + +*/ + +// TODO: figure out why firefox is being a fucker with regard to changing the heights +// of things when elements are removed from above + +Array.prototype.random = function() { + return this[Math.floor((Math.random()*this.length))]; +} + +// image fetch stuff var imageQueue = [] function fetch(){ @@ -14,35 +41,24 @@ function fetch(){ } function fetched(data){ - log("images fetched") var imageUrls = [] try { var images = data['value']['items'][0]['recent-images']['recent-image'] for(var i = 0; i < images.length; i++) - imageQueue.push(images[i]['img']) + imageQueue.push(images[i]['img']); + log(images.length + " images fetched") } catch(e) { + log("couldn't parse images :(") console.log("couldn't parse object:") console.log(data) } } -function log(m){ - $("#log").html(m) -} - -function go(){ - messagePane = $("#chat")[0] - maxImages = $("#max-images")[0] - imagePoster() - scrollToEnd() - scrollWatcher() -} - function imagePoster(){ if (!imageQueue.length){ - log("queue empty") + //log("queue empty") } else if (Paused) { - log("paused") + //log("paused") } else { log(imageQueue.length + " images in queue ... posting image") var image = imageQueue.shift() @@ -53,7 +69,7 @@ function imagePoster(){ imagePosts = 0 function imagePost(image){ - imagePosts += 1 + /*imagePosts += 1 while (imagePosts > maxImages.value) { var imgs = $(".image-post:first") if (imgs.length) { @@ -62,9 +78,9 @@ function imagePost(image){ break } imagePosts -= 1 - } + }*/ var i = $("<img/>").attr("src", image) //.load(scrollIfPossible).error(scrollIfPossible) - var d = $("<div class='image-post'/>").html("username: ") + var d = $("<div class='image-post msg'/>").html("username: ") d.append(i).appendTo("#chat") } @@ -74,53 +90,388 @@ function pausego(){ $("#pausego-button").html(Paused ? "go" : "pause") } -function isScrolledToBottom(){ - var threshold = 50; +// end image fetch stuff + +function log(html, klass){ + klass = klass || "" + klass = 'log-message ' + klass + var html = "<p class='" + klass + "'>" + html + "</p>" + scrollLog.append(html) + scrollLog[0].scrollTop = scrollLog[0].scrollHeight +} + +function go(){ + messagePane = $("#chat")[0] + maxImages = $("#max-images")[0] + scrollLog = $("#scroll-log") + $("#chat").scroll(eventScroll) + $(window).resize(windowResizedHandler) + + fetch() + + //imagePoster() + + messagesLoadFromPage() + + scrollToEnd() + setLastKnownHeight() + setLastKnownScrollTop() + messageHeightsCalc() + highlightFocusedMessage() - var containerHeight = messagePane.style.pixelHeight || messagePane.offsetHeight - var currentHeight = (messagePane.scrollHeight > 0) ? messagePane.scrollHeight : 0 + + + + //scrollWatcher() +} + +// messages stuff + +// Messages = [{element: aDomNode, complete: false, height: 24, imgs: []}, {...}, ...] +var Messages = [] + +function messageRandomButton(){ + messageRandomButton.paused = !messageRandomButton.paused + + if (messageRandomButton.paused) + clearTimeout(messageRandom.timer) + else + messageRandom() - var result = (currentHeight - messagePane.scrollTop - containerHeight < threshold); + $("#message-random-button").html((messageRandomButton.paused ? "start" : "stop") + " adding + removing messages") - return result; } +messageRandomButton.paused = true -function scrollIfPossible(){ - if (lastScriptedScrolledPosition <= messagePane.scrollTop || isScrolledToBottom()) - scrollToEnd() +function messageRandom(){ + var funcs = [messageRemoveFirst, messageAddNew, messageAddNew, messageAddNew, messageAddNew] + funcs.random()(); + messageRandom.timer = setTimeout(messageRandom, 100) +} + +function messageRandomImageButton(){ + messageRandomImageButton.paused = !messageRandomImageButton.paused + + if (messageRandomImageButton.paused) + clearTimeout(messageRandomImage.timer) + else + messageRandomImage() + + $("#message-random-image-button").html((messageRandomImageButton.paused ? "start" : "stop") + " adding + removing image messages") + +} +messageRandomImageButton.paused = true + +function messageRandomImage(){ + var funcs = [messageRemoveFirst, messageAddNew, messageAddNew, messageImageAddNew, messageImageAddNew] + funcs.random()(); + messageRandomImage.timer = setTimeout(messageRandomImage, 100) +} + +function messageRemoveFirst(){ + //$(".msg:first").remove() + var msg = Messages.shift() + $(msg['element']).remove() + LastKnown.focus -= 1 + scrollToFocus(true) +} + +function messagesLoadFromPage(){ + Messages = [] + $(".msg").each(function(){ + Messages.push({"element": this, "complete": true}) + }) +} + +function messageAddNew(nodesToAppend){ + var string = " " + Math.random() + " " + Math.random() + " " + Math.random() + " " + Math.random() + " " + Math.random() + " " + Math.random() + " " + Math.random() + " " + Math.random() + var msg = $("<div class='msg'>test" + string + "</div>") + if (nodesToAppend && nodesToAppend.length){ + for(var i = 0; i < nodesToAppend.length; i++){ + nodesToAppend[i].appendTo(msg) + } + } + msg.appendTo("#chat") + var imgs = msg.find("img") + if (imgs.length) { + complete = false + var truImgs = [] + for (var i = 0; i < imgs.length; i++){ + truImgs.push(imgs[i]) + } + Messages.push({"element": msg[0], "height": msg.outerHeight(true), "complete": false, "imgs": truImgs}) + startImageCompletenessMonitor() + // start completeness monitor + } else { + Messages.push({"element": msg[0], "height": msg.outerHeight(true), "complete": true}) + } + + scrollToFocus() +} + +function messageImageAddNew(){ + if (!imageQueue.length) + return + var image = imageQueue.shift() + var i = $("<img/>").attr("src", image) + messageAddNew([i]) + +} + +function startImageCompletenessMonitor(){ + if (!imageCompletenessMonitor.timer) { + imageCompletenessMonitor() + log("starting image completeness monitor") + } +} + +function imageCompletenessMonitor(){ + var allMessagesComplete = true + for (var m = 0; m < Messages.length; m++){ + var msg = Messages[m]; + if (!msg["complete"]){ + allMessagesComplete = false; + var messageComplete = true; + for (var i = 0; i < msg["imgs"].length; i++){ + if (!msg["imgs"][i].complete) { + messageComplete = false; + break; + } + } + msg["complete"] = messageComplete; + msg["height"] = $(msg["element"]).outerHeight(true) + } + } + if (allMessagesComplete) { + clearTimeout(imageCompletenessMonitor.timer) + imageCompletenessMonitor.timer = 0 + log("stopping image completeness monitor") + } else { + scrollToFocus() + imageCompletenessMonitor.timer = setTimeout(imageCompletenessMonitor, 500) + } +} +imageCompletenessMonitor.timer = 0 + +// i just made this a function so i can log it... +// pass an argument to set it, otherwise returns value +function keepScrollAtBottom(){ + if (arguments.length) { + log("keep scroll at bottom was: " + keepScrollAtBottom.val + ", is now: " + arguments[0], "yellow") + keepScrollAtBottom.val = arguments[0] + } + return keepScrollAtBottom.val +} + +function logScrollProps(){ + var props = "{ scrollTop: " + messagePane.scrollTop + + ", scrollHeight: " + messagePane.scrollHeight + + ", height: " + messagePane.offsetHeight + " }" +// var total = "height+scrollTop: " + (messagePane.offsetHeight + messagePane.scrollTop) + log(props) +// log(total) +} + +// "scroll" event triggered if: +// user scrolls element +// we change element's scrollTop <-- not in opera +// scrollbar at bottom and stuff removed from top <-- not in opera +// scrollbar at bottom and window resized <-- not in opera +// i don't know what happens with regards to ie & i don't care (for now) + +function eventScroll(e){ + // added 'ignoreLastScroll' so we can ignore scroll events when we change scrollTop + if (eventScroll.ignoreLastScroll && $.browser != "opera") { + eventScroll.ignoreLastScroll = false + log("pane scrolled (ignoring)", "red") + return; + } + log("pane scrolled", "green") + findScrollFocus() + logScrollProps() + highlightFocusedMessage() +} +eventScroll.ignoreLastScroll = false + +function findScrollFocus(){ + if (messagePane.scrollHeight <= messagePane.offsetHeight) // no scrollbar exists yet... content not overflowing + keepScrollAtBottom(true) + else if (isScrolledToBottom()) + keepScrollAtBottom(true) + else + keepScrollAtBottom(false) + var i = findMiddleMessage() + LastKnown.focus = i +} + +function findMiddleMessage(){ + var midpoint = (messagePane.offsetHeight / 2) + messagePane.scrollTop + var total = 0 + for(var i = 0; i < Messages.length; i++){ + total += Messages[i].height + if (total > midpoint) { + log("found middle item after " + i + " iterations") + return i + } + } + return i +} + +function highlightFocusedMessage(){ + $(".msg").removeClass("selected") + if (keepScrollAtBottom()) + $(".msg:last").addClass("selected") + else + $(".msg:eq(" + LastKnown.focus + ")").addClass("selected") +} + +function isScrolledToBottom(){ + var threshold = 20; + // is messagePane.offsetHeight instead messagePane.style.pixelHeight in ie? + return (messagePane.scrollHeight - messagePane.scrollTop - messagePane.offsetHeight < threshold); } -var lastScriptedScrolledPosition = 0 function scrollToEnd(){ + eventScroll.ignoreLastScroll = true messagePane.scrollTop = messagePane.scrollHeight - lastScriptedScrolledPosition = messagePane.scrollTop + keepScrollAtBottom(true) } -function scrollWatcher(){ - scrollIfPossible() - setTimeout(scrollWatcher, 500) +function scrollToFocus(forceUpdate){ + if (keepScrollAtBottom()) { // scroll to bottom + scrollToEnd() + highlightFocusedMessage() + } else { // scroll to focused message + if ( forceUpdate || + LastKnown.height != messagePane.offsetHeight /* was resized */ ) { + var heightToMessage = 0 + for(var i = 0; i < LastKnown.focus; i++) + heightToMessage += Messages[i].height; + heightToMessage += /*Math.floor( */Messages[i].height / 2 //) + eventScroll.ignoreLastScroll = true + // _____________ + // X = scrollTop | | + // | a | + // X____________ | <- scrollHeight + // offsetHeight -> | | b | | + // of |____________| | + // visible | c | + // frame |_____________| + // + // to set scrollTop to center item b, take distance to item b from top - (offsetHeight / 2) + // + messagePane.scrollTop = heightToMessage - /*Math.floor (*/messagePane.offsetHeight / 2/*)*/ + highlightFocusedMessage() + setLastKnownHeight() + setLastKnownScrollTop() + } + } +} + +function messageHeightsCalc() { + log("recalculated heights of each message") + for(var i = 0; i < Messages.length; i++){ + Messages[i].height = $(Messages[i].element).outerHeight(true) + } +} + +// there's a timeout so that while the window is getting resized, it doesn't recalculate constantly, which would be slow +function windowResizedHandler(){ + log("window resized") + if (windowResizedHandler.timeout) clearTimeout(windowResizedHandler.timeout); + windowResizedHandler.timeout = setTimeout(windowResized, 250) +} + +function windowResized(){ + messageHeightsCalc() + scrollToFocus() + setLastKnownHeight() + setLastKnownScrollTop() +} + +var LastKnown = { + "height": 0, "scrollTop": 0, "focusElement": 0 +} +function setLastKnownHeight(){ + LastKnown.height = messagePane.offsetHeight +} +function setLastKnownScrollTop(){ + LastKnown.scrollTop = messagePane.scrollTop } </script> <style> - #chat { width: 500px; height: 90%; overflow: scroll; } + html, body { + height: 100%; /* need dis to enable percentage heights working w/ standard doctype */ + } + #chat { + width: 500px; + height: 85%; + overflow-y: scroll; + overflow-x: hidden; + } img { max-height: 300px; } + #sidebar { + height: 85%; float: right; + } + #scroll-log { width: 300px; height: 80%; overflow: scroll; } + .log-message { + margin: 0; padding: 0; + font-size: 10px; + } + .selected, .yellow { + background-color: yellow; + } + .green { + background-color: green; + } + .red { + background-color: red; + } </style> </head> <body> + <center><h3>scrolling tests</h3></center> + <div id="sidebar"> + <div id="scroll-log"><h3>log</h3></div> + <button onclick="messageRemoveFirst()">purge oldest message</button> + <button onclick="messageAddNew()">add new message</button> + <br> + <button onclick="messageRandomButton()" id="message-random-button">start adding + removing messages</button> + <br> + <button onclick="messageImageAddNew()">add image message</button> + <br> + <button onclick="messageRandomImageButton()" id="message-random-image-button">start adding + removing image messages</button> + </div> <div id="chat"> - test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br> - test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br> - test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br> - test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br> - test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br> - test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br> + <div class="msg">test 1</div><div class="msg">test 2</div><div class="msg">test 3</div><div class="msg">test 4</div><div class="msg">test 5</div><div class="msg">test 6</div><div class="msg">test 7</div><div class="msg">test 8</div><div class="msg">test 9</div><div class="msg">test 10</div> + <div class="msg">test 11</div><div class="msg">test 12</div><div class="msg">test 13</div><div class="msg">test 14</div><div class="msg">test 15</div><div class="msg">test 16</div><div class="msg">test 17</div><div class="msg">test 18</div><div class="msg">test 19</div><div class="msg">test 20</div> + <div class="msg">test 21</div><div class="msg">test 22</div><div class="msg">test 23</div><div class="msg">test 24</div><div class="msg">test 25</div><div class="msg">test 26</div><div class="msg">test 27</div><div class="msg">test 28</div><div class="msg">test 29</div><div class="msg">test 30</div> + <div class="msg">test 31</div><div class="msg">test 32</div><div class="msg">test 33</div><div class="msg">test 34</div><div class="msg">test 35</div><div class="msg">test 36</div><div class="msg">test 37</div><div class="msg">test 38</div><div class="msg">test 39</div><div class="msg">test 40</div> + <div class="msg">test 41</div><div class="msg">test 42</div><div class="msg">test 43</div><div class="msg">test 44</div><div class="msg">test 45</div><div class="msg">test 46</div><div class="msg">test 47</div><div class="msg">test 48</div><div class="msg">test 49</div><div class="msg">test 50</div> </div> <p id="log"></p> - <button onclick="fetch()">add images</button> - <button onclick="pausego()" id="pausego-button">pause</button> + + + <br> + + + <!-- <button onclick="fetch()">add images</button> + <button onclick="pausego()" id="pausego-button">pause</button> <br /> - max image posts: <input name="max-images" id="max-images" value="50" /> + max image posts: <input name="max-images" id="max-images" value="50" /> --> +<pre> +goals: +initial state: +* keep scrollbar at bottom while new messages are added to bottom +* keep scrollbar at bottom while old messages purged from top +* keep scrollbar at bottom while window resized +if user scrolls up: +* keep central message location consistent while new messages added to bottom +* keep central message location consistent while new messages purged from top +* keep central message location consistent while window resized +* scroll back to bottom after a reasonable period of no activity (1 minute?) +</pre> </body> <script> go() |
