Christmas Day is looming!
For that, I traditionally write a web tutorial to commemorate the occasion. This year, it will be sinfully simple - a scroll-down card. Y'know, one of those things where content changes on the screen when you scroll.
In order to do that, however, we'll need to start off with a standard HTML layout.
<!DOCTYPE html>
<html>
<head>
<title>Xmas Carol</title>
<style>
</style>
<script>
</script>
</head>
<body>
</body>
</html>
Because we'll be screwing around with div layouts, let's add this to the CSS. It turns all divs a translucent
green.
<style>
div{background-color:rgba(0,255,0,0.2);}
</style>
Add a div to the body. The id will be
container.
<body>
<div id="container">
</div>
</body>
This is the style for
container. Yep, you didn't read it wrong. The width is set to 100%, and the height is 10 times the height of the screen. Which puts it at a whopping 1000%. This forces the page to be scrollable. However, if you run the code now, you might not see a damn thing even though ideally, you're supposed to see a translucent
green overlay.
<style>
div{background-color:rgba(0,255,0,0.2);}
#container
{
width:100%;
height:1000%;
}
</style>
You may not see anything until you do this. This ensures that the html and body elements are set at 100% of screen height. This is
supposed to be the default, but you know how it is with different browsers...
<style>
div{background-color:rgba(0,255,0,0.2);}
html, body
{
height:100%;
padding:0px;
}
#container
{
width:100%;
height:1000%;
}
</style>
Now here you go. This is what you should see.
padding:
0px ensures that there are no ugly spaces at the top.
OK, now let's add another div, id
view_wrapper.
<body>
<div id="container">
<div id="view_wrapper">
</div>
</div>
</body>
We style it like so. Width is set to full, height is at 80%, and there's a 5% margin at the top. Most importantly, the
position property has been set to
fixed so it will remain in the middle of your screen no matter where you scroll.
<style>
div{background-color:rgba(0,255,0,0.2);}
html, body
{
height:100%;
padding:0px;
}
#container
{
width:100%;
height:1000%;
}
#view_wrapper
{
width:100%;
height:80%;
position:fixed;
margin-top:5%;
}
</style>
Try it!
One more div nested. Set the id to
view.
<body>
<div id="container">
<div id="view_wrapper">
<div id="view">
</div>
</div>
</div>
</body>
Style it like this. It occupies 80% of
view_wrapper's width, all of its height, and the margin property is set to center it. Properties such as text-align,
font-size,
font-family and
font-weight are optional. Do what you want with those - unless you get really extreme, it won't change the functionality much.
<style>
div{background-color:rgba(0,255,0,0.2);}
html, body
{
height:100%;
padding:0px;
}
#container
{
width:100%;
height:1000%;
}
#view_wrapper
{
width:100%;
height:80%;
position:fixed;
margin-top:5%;
}
#view
{
width:80%;
height:100%;
margin: 0 auto 0 auto;
text-align:center;
font-size:5em;
font-family:verdana;
font-weight:bold;
}
</style>
That's what it should look like now. The
view div is meant to contain the content of your text!
Now we're going to write some JavaScript to populate the
view div. We start off by declaring a variable lyrics, and populate it with lines from a standard Christmas song. Note that each line is denoted with a "|" symbol.
<script>
var lyrics="We wish you a Merry Christmas|We wish you a Merry Christmas|We wish you a Merry Christmas|And a Happy New Year!";
</script>
Next, we create an array named lines, and set it to contain each line of the lyrics, using the "|" symbol.
<script>
var lyrics="We wish you a Merry Christmas|We wish you a Merry Christmas|We wish you a Merry Christmas|And a Happy New Year!";
var lines=lyrics.split("|");
</script>
Next, we write a function
getScrollPercentage(). It will return the percentage of how much you've scrolled from the top.
window.innerHeight has to be multiplied by 10 in this case because you're measuring it against the entire scrollable content, which is 10 times your screen height. The height of
container was set at 1000% percent, remember?
So when you reach the bottom, it should be 100%, right?
Wrong! And I'll prove it.
<script>
var lyrics="We wish you a Merry Christmas|We wish you a Merry Christmas|We wish you a Merry Christmas|And a Happy New Year!";
var lines=lyrics.split("|");
function getScrollPercentage()
{
return (document.documentElement.scrollTop/(window.innerHeight*10))*100;
}
</script>
Add this to your code, and run it.
<body onscroll="console.log(getScrollPercentage())">
Check your console (Ctrl+Shift+i should do it) and you'll see when you scroll all the way to the bottom, the readout is 90% at most.
So add this line. This is to help decide what percentage of scrollable content to be dedicated to each line, ie. each item in the lines array. Instead of 100%, we only have 90% to work with, so it's (90/lines.length). Since we want to err on the side of caution, we'll round it down using
Math.floor().
var lines=lyrics.split("|");
var segment_per_line=Math.floor(90/lines.length);
function getScrollPercentage()
{
return (document.documentElement.scrollTop/(window.innerHeight*10))*100;
}
Now we write the function
scrolling(). We set a variable,
scrolled, to the current scrolled percentage defined in
getScrollPercentage(). Then we get the line number, another variable called
line_no, by dividing scrolled with
segment_per_line. Erring on the side of caution, again, we use
Math.floor() to round it down. Then we set the
innerHTML property of the view div to the appropriate item of the lines array.
function getScrollPercentage()
{
return (document.documentElement.scrollTop/(window.innerHeight*10))*100;
}
function scrolling()
{
var scrolled=getScrollPercentage();
var line_no=Math.floor(scrolled/segment_per_line);
document.getElementById("view").innerHTML=lines[line_no];
}
Change this line, and run your code.
<body onscroll="scrolling()">
So you should get different lines at different scroll heights! Try scrolling all the way down from the top.
The problem with this...
...is that it's not terribly friendly. You scroll, an entire line appears, then as you scroll, it's replaced by a whole new line just like that. In the case of our chosen stanza, the first three lines are identical! How you you even tell when one line changes to the next?!
The answer: Make each word appear individually, by line.
For that to happen, add this to your code. It runs through each item in the lines array and turns it into an array too. So instead of an array of lines, you have an array of arrays, and each of these sub arrays will contain the words to the lines, in sequence!
var lines=lyrics.split("|");
var segment_per_line=Math.floor(90/lines.length);
for (var i=0;i<lines.length;i++)
{
lines[i]=lines[i].split(" ");
}
function getScrollPercentage()
{
return (document.documentElement.scrollTop/(window.innerHeight*10))*100;
}
In other words, instead of this...
lines[0] "We wish you a Merry Christmas"
lines[1] "We wish you a Merry Christmas"
lines[2] "We wish you a Merry Christmas"
lines[3] "And a Happy New Year!"
...you get this!
lines[0] ["We","wish","you","a","Merry","Christmas"]
lines[1] ["We","wish","you","a","Merry","Christmas"]
lines[2] ["We","wish","you","a","Merry","Christmas"]
lines[3] ["And","a","Happy","New","Year!"]
Now modify your
scrolling() function. The variable
segment_completion gives you how many total segments has been completed already. So, for example, if you're at line number 3 and each line is 5% of scroll completion, you have scrolled at least 15%! The next line would be at 20%, so how many words of the current line you display will be the progress you've made from 15% to 20%.
function scrolling()
{
var scrolled=getScrollPercentage();
var line_no=Math.floor(scrolled/segment_per_line);
var segment_completion=line_no*segment_per_line;
document.getElementById("view").innerHTML=lines[line_no];
}
For that, we'll create a few function,
getWordsFromLine(). We'll use this function to determine how many words from the current line (which is now an array, remember?) to display, and display those words instead of the entire line. For that, we pass in the entire sub-array from the
lines array which will be defined as
lines[line_no], and a value which gives you the percentage scrolled since the last completed segment.
function getWordsFromLine(line,completion)
{
}
function scrolling()
{
var scrolled=getScrollPercentage();
var line_no=Math.floor(scrolled/segment_per_line);
var segment_completion=line_no*segment_per_line;
var words=getWordsFromLine(lines[line_no], scrolled-segment_completion));
document.getElementById("view").innerHTML=words;
}
Here, we calculate the completion percentage of the current segment, and assign it to the
completion_ratio variable. Then we use this ratio to determine how many words out of the entire line will be displayed, using the number of words (
line.length) and
completion_ratio.
function getWordsFromLine(line,completion)
{
var completion_ratio=(completion/segment_per_line);
var words_displayed=Math.ceil(line.length*completion_ratio);
var words="";
}
After that, we iterate through the line array using a
For loop, adding the necessary number of words, and return the string generated.
function getWordsFromLine(line,completion)
{
var completion_ratio=(completion/segment_per_line);
var words_displayed=Math.ceil(line.length*completion_ratio);
var words="";
for (var i=0;i<words_displayed;i++)
{
words+=line[i]+" ";
}
return words;
}
Run your code! What happens? Bet it worked, didn't it? But you may run into a small problem near the end because
lines[line_no] may be undefined due to
line_no being larger than the total number of items in
lines. So just do this...
function scrolling()
{
var scrolled=getScrollPercentage();
var line_no=Math.floor(scrolled/segment_per_line);
var segment_completion=line_no*segment_per_line;
var words=(lines[line_no]==undefined?"":getWordsFromLine(lines[line_no],scrolled-segment_completion));
document.getElementById("view").innerHTML=words;
}
What it basically does is check if
lines[line_no] is undefined, and returns an empty string if so. If not, run the
getWordsFromLine() function as per normal.
Further improvements?
Well, now your lines appear word by word, but they skip onwards to the next line without pause. That's really unnatural.
So just modify your code like so. Instead of a single "|" symbol, we use a double "||". This ensures that the lines array has more items, specifically an empty item between each line of the song.
var lyrics="We wish you a Merry Christmas||We wish you a Merry Christmas||We wish you a Merry Christmas||And a Happy New Year!||Good tidings to you||To you and your kin||Good tidings for Christmas||And a Happy New Year!";
Run your code. Is there a nice pause between each line? But let's add an
If conditional to this. As in, the content only changes if there are actually words to display. Re-run your code. Now there won't be any blank pauses between each line!
function scrolling()
{
var scrolled=getScrollPercentage();
var line_no=Math.floor(scrolled/segment_per_line);
var segment_completion=line_no*segment_per_line;
var words=(lines[line_no]==undefined?"":getWordsFromLine(lines[line_no],scrolled-segment_completion));
if (words.length>0)
{
document.getElementById("view").innerHTML=words;
}
}
So now you have a nice scrolling card. Let's change the background a bit to remove the placeholder green.
div{background-color:rgba(0,255,0,0.0);}
Then we'll add this tile to spruce things up.
|
xmas2016_bg.jpg |
body
{
background:url(xmas2016_bg.jpg);
}
Nice, right?!
Little extras - a mini caroller
Now, for all intents and purposes, your scrolling card is done. But here's a little something to complement the look. I've added the HTML and CSS for a little South Park type caroller. I'm not going to show you how it's done because that's totally not the point of this tutorial at all, but here's the code anyway.
<div id="view_wrapper">
<div id="view">
</div>
<div id="caroller">
<div class="head" id="head">
<div class="hair">
</div>
<div class="mouth" id="mouth">
</div>
</div>
<div class="body">
<div class="sheet">
</div>
</div>
<div class="feet">
</div>
</div>
</div>
Here's the CSS. Change the colors if you like.
#view
{
width:80%;
height:100%;
margin: 0 auto 0 auto;
text-align:center;
font-size:5em;
font-family:verdana;
font-weight:bold;
}
#caroller
{
width:200px;
height:300px;
margin: -20% auto 0 auto;
}
#caroller .head
{
width:100px;
height:100px;
background-color:#AA4400;
border-radius:50%;
margin: 0 auto 0 auto;
}
#caroller .head .hair
{
width:100%;
height:50%;
}
#caroller .head .hair::before,#caroller .head .hair::after
{
content:"";
display:block;
width:60%;
height:100%;
background-color:#000000;
}
#caroller .head .hair::before
{
border-radius:70% 0%;
float:left;
margin-left:-10%;
}
#caroller .head .hair::after
{
border-radius:0% 70%;
float:right;
margin-right:-10%;
}
#caroller .head .mouth
{
width:0px;
height:0px;
background-color:#000000;
border-radius:50%;
margin: 10% auto 0 auto;
}
#caroller .body
{
width:180px;
height:150px;
background-color:#AA0000;
border-radius:40% 40% 0% 0%;
margin: -5px auto 0 auto;
padding-top:10px;
}
#caroller .body::before,#caroller .body::after
{
content:"";
display:block;
width:50px;
height:50px;
border-radius:50%;
background-color:#FFFFFF;
}
#caroller .body::before
{
float:left;
margin-left:-10%;
margin-top:50px;
}
#caroller .body::after
{
float:right;
margin-right:-10%;
margin-top:-30px;
}
#caroller .sheet
{
width:80%;
height:50%;
background-color:#FFFF00;
margin: 0 auto 0 auto;
}
#caroller .feet
{
width:150px;
height:50px;
background-color:#440000;
margin: 0 auto 0 auto;
}
#caroller .feet::before,#caroller .feet::after
{
content:"";
display:block;
width:60%;
height:80%;
border-radius:50% 50% 0% 0%;
background-color:#220000;
margin-top:10px;
}
#caroller .feet::before
{
float:left;
margin-left:-10%;
}
#caroller .feet::after
{
float:right;
margin-right:-10%;
}
Cute little bugger, isn't he?!
Now do this to your JavaScript. This ensures that the head and mouth move in random directions when there are words to "sing", and stays still otherwise.
function generaterandomno(varmin,varmax)
{
return Math.floor((Math.random() * (varmax-varmin+1)) + varmin);
}
function scrolling()
{
var scrolled=getScrollPercentage();
var line_no=Math.floor(scrolled/segment_per_line);
var segment_completion=line_no*segment_per_line;
var words=(lines[line_no]==undefined?"":getWordsFromLine(lines[line_no],scrolled-segment_completion));
if (words.length>0)
{
document.getElementById("view").innerHTML=words;
document.getElementById("mouth").style.height=generaterandomno(10,30)+"px";
document.getElementById("mouth").style.width=generaterandomno(10,50)+"px";
document.getElementById("head").style.transform="rotate("+generaterandomno(-5,5)+"deg)";
document.getElementById("head").style.webkitTransform="rotate("+generaterandomno(-5,5)+"deg)";
}
else
{
document.getElementById("mouth").style.height="0px";
document.getElementById("mouth").style.width="0px";
}
}
Merry Christmas!
You can vary things by changing the lyrics. It should still work unless you substitute the lyrics with something long and rambling (like one of
Roy Ngerng's speeches) going over 100 lines or so.
That's how I (sc)roll, baby.
T___T