summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--static/tests/scrolling.html437
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()