代码改变世界

javascript Dom 编程艺术:ANIMATED SLIDESHOW

2012-08-28 16:41  youxin  阅读(491)  评论(0编辑  收藏  举报

Animation is the perfect example of this kind of change. In a nutshell, animation involves
changing an element’s position over time.

Increments
Moving an element by 150 pixels after an interval of five seconds is a sort of animation,
albeit a very primitive one. Effective animation uses incremental movement. Moving from
the starting position to the final location should happen in a series of steps rather than
one quick jump.

 

function moveElement 函数如下:

function moveElement(elementID,final_x,final_y,interval)
{
    if(!document.getElementById(elementID)) return false;
    var elem=document.getElementById(elementID);
    if(elem.movement)
    {
        clearTimeout(elem.movement);
    }
    var xpos=parseInt(elem.style.left);
    var ypos=parseInt(elem.style.top);
    if(xpos==final_x&&ypos==final_y)
    {
        return true;
    }
    if(xpos<final_x)
    {
        xpos++;
    }
    if(xpos>final_x)
    {
        xpos--;
    }
    if(ypos<final_y)
    {
        ypos++;
    }
    if(ypos>final_y)
    {
        ypos--;
    }
    elem.style.left=xpos+"px";
    elem.style.top=ypos+"px";
    var repeat="moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
    elem.movement=setTimeout(repeat,interval);
}

为什么用elem.style.top,left始终为Nan,看文章:

一个非常好的实用的函数。

"moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")"
  That’s a lot of concatenating! Rather than inserting that long string directly into the setTimeout function, assign the string to a variable called repeat.
var repeat ="moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
Now you can simply insert repeat as the first argument of the setTimeout function. The
second argument is the length of the pause before the first argument is called. This used
to be hardcoded as ten milliseconds. Now it’s whatever value is contained by the variable
interval:
movement = setTimeout(repeat,interval);
Close the function with a curly brace:

要创建一个类式下面的demo:

当鼠标在链接上面时,下面的图片会滚动到相应的图片。

Make a composite image of all the previews 一张总图片。
Hide most of this image
When the user hovers over a link, display just a part of the image
I’ve made a composite image of the three previews plus one default view:

这张总图片叫topics.gif,我们想要当鼠标over link时,只显示图片的一部分。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
</head>

<body>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Web Design</title>
</head>
<body>
<h1>Web Design</h1>
<p>These are the things you should know.</p>
<ol id="linklist">
<li>
<a href="structure.html">Structure</a>
</li>
<li>
<a href="presentation.html">Presentation</a>
</li>
<li>
<a href="behavior.html">Behavior</a>
</li>
</ol>

<img src="topics.gif" alt="building blocks of web design"  id="preview" />
</body>
</html>

Right now, the entire image is visible. I only want a 100 by 100 pixel portion to be visible
at any one time. I can’t do that with JavaScript, but I can do it with CSS.

CSS
The CSS overflow property dictates how content within an element should be displayed
when the content is larger than its container element. When an element contains content
that is larger than itself, there is an overflow. In that situation, you can clip the content so
that only a portion of it is visible. You can also specify whether or not the web browser
should display scroll bars, allowing the user to see the rest of the content.
There are four possible values for the overflow property: “visible”, “hidden”, “scroll”, and
“auto”.
If the overflow of an element is set to “visible”, then no clipping occurs. The content
overflows and is rendered outside the element.
A value of “hidden” will cause the excess content to be clipped. Only a portion of
the content will be visible.
The “scroll” value is similar to “hidden”. The content will be clipped but the web
browser will display scroll bars so that the rest of the content can be viewed.
A value of “auto” is just like “scroll” except that the scroll bars will only be displayed
if the content overflows its container element. If there is no overflow, no
scroll bars appear.
Of these four values, “hidden” sounds like the most promising for my purposes. I want to
display just a 100 by 100 pixel (100*100像素)portion of an image that is 400 by 100 pixels in size.

把图片包围在一个div里:

<div id="slideshow">
<img src="topics.gif" alt="building blocks of web design"  id="preview" />
</div>

样式表如下:

<style type="text/css">
#slideshow{
    width:100px;
    height:100px;
    position:relative;
    overflow:hidden;
}
</style>

Now only a portion of topics.gif is visible. Currently, only the first 100 pixels are visible.

<script>
function moveElement(elementID,final_x,final_y,interval)
{
    if(!document.getElementById(elementID)) return false;
    var elem=document.getElementById(elementID);
  var xpos=parseInt(elem.style.left);
    var ypos=parseInt(elem.style.top);
    if(xpos==final_x&&ypos==final_y)
    {
        return true;
    }
    if(xpos<final_x)
    {
        xpos++;
    }
    if(xpos>final_x)
    {
        xpos--;
    }
    if(ypos<final_y)
    {
        ypos++;
    }
    if(ypos>final_y)
    {
        ypos--;
    }
    elem.style.left=xpos+"px";
    elem.style.top=ypos+"px";
    var repeat="moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
    movement=setTimeout(repeat,interval);
}
function prepareSlideshow()
{
    var preview=document.getElementById("preview");
    preview.style.position="absolute";
    preview.style.left="0px";
    preview.style.top="0px";
    // Get all the links in the list
    var list = document.getElementById("linklist");
    var links = list.getElementsByTagName("a");
    // Attach the animation behavior to the mouseover event
    links[0].onmouseover = function() {
    moveElement("preview",-100,0,10);
    }
    links[1].onmouseover = function() {
    moveElement("preview",-200,0,10);
    }
    links[2].onmouseover = function() {
    moveElement("preview",-300,0,10);
    }
  
}

window.onload=prepareSlideshow;
</script>

上面的代码有问题:

something’s not quite right. If you move quickly from link to the link, the animation
becomes confused. There’s something wrong with the moveElement function. 如果很快的在link移动,图片没有反应。

A question of scope
The animation problem is being caused by a global variable.
When I abstracted the moveMessage function and turned it into the moveElement function,
I left the variable movement as it was:

var repeat ="moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
movement = setTimeout(repeat,interval);全局变量

(注意:

function test()
{
    str="sming";
}
test();
alert(str);

上面的 变量str我在函数内之间调用会被js当做是全局变量,会输出str的值)。

 

This is causing a problem now that the moveElement function is being called whenever the
user hovers over a link. Regardless of whether or not the previous call to the function has
finished moving the image, the function is being asked to move the same element somewhere
else. In other words, the moveElement function is attempting to move the same element
to two different places at once and the movement variable has become the rope in a
tug of war. As the user quickly moves from link to link, there is a backlog of events building
up in the setTimeout queue.
I can flush out this backlog(冲掉未完成的工作) by using clearTimeout:
clearTimeout(movement);
But if this statement is executed before movement has been set, I’ll get an error.

 

I can’t use a local variable:
var movement = setTimeout(repeat,interval);
If I do that, then the clearTimeout statement won’t work; the movement variable will no
longer exist.
I can’t use a global variable. I can’t use a local variable. I need something in between. I
need a variable that applies just to the element being moved.
Element-specific variables do exist. In fact, you’ve been using them all the time. What I’ve
just described is a property.
Until now, you’ve used properties provided by the DOM:
element.firstChild
element.style
etc.
You can also assign your own properties:

element.property = value

If you wanted, you could create a property called foo with a value of “bar”:
element.foo = "bar";
It’s just like creating a variable. The difference is that the variable belongs just to that element.

I’m going to change movement from being a global variable to a property of the element
being moved, elem. That way, I can test for its existence and, if it exists, use clearTimeout

function moveElement(elementID,final_x,final_y,interval) {
  if (!document.getElementById) return false;
  if (!document.getElementById(elementID)) return false;
  var elem = document.getElementById(elementID);
  if (elem.movement) {
    clearTimeout(elem.movement);
  }
  var xpos = parseInt(elem.style.left);
  var ypos = parseInt(elem.style.top);

  if (xpos == final_x && ypos == final_y) {
    return true;
  }
  if (xpos < final_x) {
    xpos++;
  }
  if (xpos > final_x) {
    xpos--;
  }
  if (ypos < final_y) {
    ypos++;
  }
  if (ypos > final_y) {
    ypos--;
  }
  elem.style.left = xpos + "px";
  elem.style.top = ypos + "px";
  var repeat = "moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
  elem.movement = setTimeout(repeat,interval);
}

 

Refining the animation
The moveElement function moves an element one pixel at a time until it reaches the coordinates
specified by the final_x and final_y arguments. The movement is smooth, but
it’s also kind of boring.
I’m going to spice up the animation a bit.
Take a look at this simple bit of code in moveElement.js:
if (xpos < final_x) {
xpos++;
}

No matter how far away the element is from its final position, it will always move toward
it one pixel at a time. I’m going to change that. If the element is far away from its final
position, I want it to move a large distance. If the element is near to its final position, I
want it to move a short distance.
First of all, I need to figure out how far the element is from its final destination. If xpos is
less than final_x, I want to know by how much. I can find out by subtracting xpos, the
current left position, from final_x, the desired left position:

var dist = final_x - xpos;

That’s the distance that the element needs to travel. I’m going to move the element onetenth
of this distance. I’ve chosen one-tenth as a nice round fraction. You can try other
values if you like:
var dist = (final_x - xpos)/10;
xpos = xpos + dist;
This will move the element one-tenth of the distance it needs to go.

 

A problem occurs when the distance between xpos and final_x is less than 10. When that
value is divided by 10, the result will be less than one. You can’t move an element by less
than one pixel

Math.floor(number)
Math.round(number)

Math.ceil(number)

For the moveElement function, I’m going to round upward. If I used floor or round, the
element might never reach its final destination:
var dist = Math.ceil((final_x - xpos)/10);
xpos = xpos + dist;
This covers the situation when xpos is less than final_x:
if (xpos < final_x) {
var dist = Math.ceil((final_x - xpos)/10);
xpos = xpos + dist;
}

<script>

function test()
{
    str="sming";
}
test();
alert(str);

function moveElement(elementID,final_x,final_y,interval)
{
    if(!document.getElementById(elementID)) return false;
    var elem=document.getElementById(elementID);
    if(elem.movement)
    {
        clearTimeout(elem.movement);
    }
    var xpos=parseInt(elem.style.left);
    var ypos=parseInt(elem.style.top);
    
     
    
    
    if(xpos==final_x&&ypos==final_y)
    {
        return true;
    }
    if(xpos<final_x)
    {
        var dist = Math.ceil((final_x - xpos)/10);
        xpos = xpos + dist;
    }
    if(xpos>final_x)
    {
        var dist = Math.ceil((xpos - final_x)/10);
        xpos = xpos - dist;
    }
    if (ypos < final_y) 
    {
     var dist = Math.ceil((final_y - ypos)/10);
     ypos = ypos + dist;
    }
    if (ypos > final_y) 
    {
      var dist = Math.ceil((ypos - final_y)/10);
      ypos = ypos - dist;
    }
    elem.style.left=xpos+"px";
    elem.style.top=ypos+"px";
    var repeat="moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
    elem.movement=setTimeout(repeat,interval);
}

The animation now feels much smoother and snappier. When you first hover over a link,
the image jumps quite a distance. As the image approaches its final destination, it “eases”
into place.
The markup, the CSS, and the JavaScript all come together to create this slideshow effect.
Everything is working fine, but there’s always room for some small tweaks.

 

 final touches 最后的修饰

The moveElement function is working really well now. There’s just one thing that bothers
me. There is an assumption being made near the start of the function:
                  var xpos = parseInt(elem.style.left);
                 var ypos = parseInt(elem.style.top);
I’m assuming that the element elem has a left style property and a top style property. I
should really check to make sure that this is the case.
If the left or top properties haven’t been set, I have a couple of options. I could simply
exit the function there and then:
if (!elem.style.left || !elem.style.top) {
return false;
}
If JavaScript can’t read those properties, then the function stops without throwing up an
error message.

 Another solution is to apply default left and top properties in the moveElement function.

If either property hasn’t been set, I can give them a default value of “0px”:

 

if (!elem.style.left) {
elem.style.left = "0px";
}
if (!elem.style.top) {
elem.style.top = "0px";
}