Web Apps: An Animation
7 min read
Create a window containing the string "Hello World! " (the trailing space is significant).
Make the text appear to be rotating right by periodically removing one letter from the end of the string and attaching it to the front. When the user clicks on the (windowed) text, it should reverse its direction.
This will be the result:
Yes, a marvolous flickering, blinking, neon-colored banner straight from the early 2000's!
Click here for the hosted version.
First we need to understand how we can create some dynamic refresh of the page, i. e. executing a function periodically without "clicking" a button. For this purpose, we can use the
+Auto prefix classes.
+Auto prefix classes
These two classes are usually used together.
+Click is used to "click a button" after a certain amount of time has passed (in milli seconds).
+Auto is a prefix class that automatically presses a button periodically; when called with 'This', it presses itself.
When we check the dependency tree of
+Auto, we can see that it inherits from the
: (dep '+Auto) +JS +Auto
Let's test a minimal example of a
+Click +Auto-button in a file called
(app) (action (html 0 "Animation" "@lib.css" NIL (form NIL (gui '(+Click +Auto +Button) 500 'This 1000 "Start") ) ) )
Now we start the server with
$ pil @lib/http.l @lib/xhtml.l @lib/form.l --server 8080 + and point or browser to localhost:8080/click-button.l. We should see a lonely little button at the top left of our browser window.
What happens when we click this button? Let's open the developer tools of the browser (
F12 in Firefox) and click on the network analysis tab.
Analyzing the network traffic
After clicking the button, this is what we see:
In the first line, we can see a
POSTrequest that redirects the page as response (status: 303). This means that a new session is created (more about PicoLisp sessionshere).
Then we see four
- the actual web page
- the css file
- the browser icon.
Then there is one
POSTrequests per second, initiated by
From the class definitions of
+Auto, we know that the first
POST is initiated by the
+Click class, and all the rest by
+Auto is inheriting from
Let's take a few moments to understand what is happening here. If you click on "Initiator" in the network tab, you can see which lines in
form.js are actually calling the page reload:
We are having an
onsubmit call from
the click-button.l on the webpage, which calls
form.js and then
Clicking on any of these lines opens the debugger tab at the relevant file position.
If you follow the code, you can see that there is not much happening - just the page gets reloaded with the updated form content. (In our example, there is no form content though except for the submit button).
Creating the animation start button
Now let's use our new knowledge. With this line of code:
(gui '(+Click +Auto +Button) 500 'This 1000 "Start")
we get a button which is labelled "Start". When we click it, we submit the form and start a session (if there isn't any yet). This is happening immediately after clicking it.
Then, 500 ms seconds later, we see the first
form.js post initiated by
+Auto, followed by periodically generated POST every 1000 ms.
Why is this useful for animations?
This functionality allows us to update all elements of the
+gui class on the front-end side without reloading the full page from the server. This avoids the flickering and the response is much faster and smoother.
Now let's create the actual content that should be updated.
Creating the "Hello World"-Element
Let's define a global variable
(setq *Str "Hello World! ")
and set it as textfield element. We use the prefix-class
+View to specify the content and call
+TextField without any further arguments, which results in a simple text field without input:
(gui '(+View +TextField) '*Str)
Rotating the string with
The text should rotate "left to right", like this:
- "Hello World! "
- " Hello World!"
- "! Hello World"
How can we do this?
As you might now, PicoLisp has many powerful list functions. For example, there is a built-in function called
rot that rotates the element by right shift. It takes an optional
cnt argument to rotate more than one element. The
rot function is destructive, which means that the original list is modified.
However, what we currently have is only a string. As first step, we need to create a list from our string using
chop. Open the REPL and test it (
$ pil +):
: (setq *Str (chop "Hello World! ")) -> ("H" "e" "l" "l" "o" " " "W" "o" "r" "l" "d" "!" " ")
Now let's rotate the
*Str list and check it:
: (rot *Str) -> (" " "H" "e" "l" "l" "o" " " "W" "o" "r" "l" "d" "!") : *Str -> (" " "H" "e" "l" "l" "o" " " "W" "o" "r" "l" "d" "!")
As you can see, the trailing space has moved to the beginning of the string. Our string length is 13 characters, so if we execute
rot *Str 12 times, we can move it back. We can control the number of executions with the
: (do 12 (rot *Str)) -> ("H" "e" "l" "l" "o" " " "W" "o" "r" "l" "d" "!" " ")
Creating the animation
Now we're almost done. We only need to transform our list
*Str back to a string using
(gui '(+View +TextField) '(pack (rot *Str))))
It works! If we click on the button, we see a (very primitive) animation.
Creating the reverse button
According to the task description, we should implement a button to reverse the rotation direction. As we saw before, we can control that by rotating 12 times instead of one. So the easiest way to do that is to define a global variable
*Dir that can be either 1 or 12. Let's initialize it to
1 using the
and modify our
+TextField so that the rotation is controlled by a
do loop which depends on
(gui '(+View +TextField) '(pack (do *Dir (rot *Str))))
Finally, we also need to define a button that toggles between
(gui '(+Button) "Reverse direction" '(setq *Dir (if (= 1 *Dir) 12 1)))
Finally, we could even toggle the button label depending on the current value of
(gui '(+Button) '(if (= 1 *Dir) "Rotate left" "Rotate right") '(setq *Dir (if (= 1 *Dir) 12 1)) )
Now it looks like this:
Let's give it some styling!
Finished, but let's make it at least a little bit more pretty!
First, I always like to include the bootstrap classes to make positioning and alignment a little bit easier.
So if the session would be already running, the button would click itself automatically and we wouldn't need to show it. So let's do two things:
- insert an auto-session start by replacing
((and (app) *Port% (redirect (baseHRef) *SesId *Url)).
- Hide hide the "start animation" button using the bootstrap "invisible" class(which simply sets the CSS "visibility" property to "hidden"). Also, let's replace our "rotate left" and "rotate right" by icons.
- insert an auto-session start by replacing
Last step, let's add some fancy flickering and neon design for fun (credits to the sources below)!
And that's it:
You can download the final solution from this link. In the same folder you will also find the custom CSS file with the neon animation.
Icons made by ZAK from www.flaticon.com