Compiling to Javascript and running in the browser |
Kotlin comes with a compiler that compiles Kotlin source code to Javascript, ready to be included in a webpage. You can thus create applications that run entirely inside a web browser.
To get you started, here are some tips and an example project.
We start by creating a new directory. We will need to create four files in this directory:
Let's start with the last one: kotlin.js is contained in the file kotlin-jslib.jar that came with the Kotlin compiler. Extract kotlin.js with your favorite tool and place it in your new directory (jar files are simply zipped archives).
The file jscanvas.js is also easy to obtain: it is contained in the archive cs109-jslib.jar that you installed earlier.
Here is a sample HTML file (canvas.html):
<html> <head> <meta charset="utf-8"> <script src="kotlin.js"></script> <script src="jscanvas.js"></script> <script src="canvas.js"></script> </head> <body onload="javascript:canvas.canvas.start();"> <canvas width="600" height="300" id="canvas"></canvas> <div id="text">This is a fun paragraph. </div> </body> </html>
In the header of the document, we load the two libraries and our own Javascript code canvas.js (which we still have to generate). In the body, we include two elements: an HTML canvas, which is a rectangular area on that we can draw freely from Javascript, and a text division (which we can update later from Javascript).
Load this file from your browser now: you should see an empty white rectangle with the text below.
At this point we should turn on the Javascript console in the web browser. This is where all output from Javascript, as well as any error messages, will appear. On Firefox, use Menu, Developer, Web Console. On Safari, go to the Settings and tick the "Show Develop menu" checkbox, then go to the newly appearing Develop menu and open the console.
So far, the Console should only show an error message:
TypeError: canvas.canvas is undefinedThis is the response to the onload attribute in the body tag. Here we are instructing Javascript to execute the function start in the package canvas inside the canvas.js script, after the webpage has been loaded entirely (which is important, since we want all HTML elements to be present when the code is run).
It remains to write this start function and to generate canvas.js. Here is a first example (canvas1.kt):
package canvas import org.otfried.cs109js.JsCanvas import org.otfried.cs109.Color import kotlin.browser.document import org.w3c.dom.* fun start() { println("Hello World from Javascript") val canvas = JsCanvas("canvas") canvas.clear(Color.GREEN) val text = document.getElementById("text") text?.appendChild(document.createTextNode("Was du hier liest ist kein Gedicht.")) }
We need to compile this file to Javascript by saying:
$ ktc-js -output canvas.js canvas1.kt
ktc-js is the Kotlin-to-Javascript compiler. I indicate the output file name canvas.js. This translates to the module name canvas when called from Javascript. The package declaration at the beginning of canvas1.kt places the start function in the package canvas, and so it needs to be called from Javascript as
canvas.canvas.start();The first canvas is the name of the Javascript file canvas.js, the second one is the package name. We call this function in this way from the onload attribute.
When you reload the webpage now, the canvas rectangle changes its color to green, and the text underneath changes. In the Javascript console, you should see the output of the println statement.
Let's make our drawing more interesting by adding some color, text, and transparency (canvas2.kt):
package canvas import org.otfried.cs109js.JsCanvas import org.otfried.cs109.Color object Controller { val canvas = JsCanvas("canvas") var x = 30.0 var y = 50.0 var alpha = 45.0 fun draw() { canvas.clear(Color.WHITE) canvas.setAlpha(48) canvas.setColor(Color.GREEN) canvas.drawRectangle(10.0, 10.0, 100.0, 100.0) canvas.setAlpha(255) // opaque for (i in 0 .. 5) { for (j in 0 .. 5) { canvas.setColor(Color(Math.floor(255-42.5*i), Math.floor(255-42.5*j), 0)) canvas.drawRectangle(150.0 + j*25.0, i*25.0, 25.0, 25.0) } } canvas.translate(x, y) canvas.setAlpha(128) canvas.rotate(alpha) canvas.setColor(Color.RED) canvas.setFont(32.0) canvas.drawText("Lovely", 0.0, 0.0) } } fun start() { println("Canvas2 starting...") Controller.draw() }
We compile again:
$ ktc-js -output canvas.js canvas2.ktWhen you reload the webpage now, the canvas should contain a transparent green square, some half-transparent red text on top, and a nice color pattern to the right.
So far so good, now let's add some interactivity. When you click with the mouse in the canvas, the text should move to this position. The keys 'a', 's', 'w', and 'z' can be used to move the text around, the keys 'j', and 'k' rotate it (canvas3.kt):
package canvas import org.otfried.cs109js.JsCanvas import org.otfried.cs109.Color import kotlin.browser.window import org.w3c.dom.events.* object Controller { val canvas = JsCanvas("canvas") var x = 30.0 var y = 50.0 var alpha = 45.0 fun draw() { canvas.save() canvas.clear(Color.WHITE) canvas.setAlpha(48) canvas.setColor(Color.GREEN) canvas.drawRectangle(10.0, 10.0, 100.0, 100.0) canvas.setAlpha(255) // opaque for (i in 0 .. 5) { for (j in 0 .. 5) { canvas.setColor(Color(Math.floor(255-42.5*i), Math.floor(255-42.5*j), 0)) canvas.drawRectangle(150.0 + j*25.0, i*25.0, 25.0, 25.0) } } canvas.translate(x, y) canvas.setAlpha(128) canvas.rotate(alpha) canvas.setColor(Color.RED) canvas.setFont(32.0) canvas.drawText("Lovely", 0.0, 0.0) canvas.restore() } fun keyDown(e: Event) { val ek = e as KeyboardEvent var k = ek.key if (k === undefined) k = "${ek.keyCode.toChar().toLowerCase()}" when (k) { "a" -> x -= 3 "s" -> x += 3 "w" -> y -= 3 "z" -> y += 3 "j" -> alpha += 10.0 "k" -> alpha -= 10.0 else -> return } draw() e.preventDefault() } fun mouseDown(e: Event) { val em = e as MouseEvent x = em.offsetX y = em.offsetY draw() } } fun start() { println("Canvas3 starting...") println("Active keys are aswzjk") Controller.draw() window.addEventListener("keydown", { Controller.keyDown(it) }, true) window.addEventListener("mousedown", { Controller.mouseDown(it) }, true) }
We compile again:
$ ktc-js -output canvas.js canvas3.ktReload the webpage. You should now be able to click into the canvas and see the text move to this position. When you press one of the active keys, the text should move and rotate.
Finally, let's add some animation to our drawing. The key 'g' starts the animation and pauses it again. The code now also resizes the canvas to fill the entire browser window, leaving just a little space for the scroll bars and the text underneath (canvas4.kt):
package canvas import org.otfried.cs109js.JsCanvas import org.otfried.cs109.Color import kotlin.browser.window import org.w3c.dom.events.* object Controller { val canvas = JsCanvas("canvas") // change canvas size to fill entire browser window init { canvas.canvas.width = window.innerWidth.toInt() - 20 canvas.canvas.height = window.innerHeight.toInt() - 50 } var x = 30.0 var y = 50.0 var alpha = 45.0 var animate = false var timeStamp = 0.0 fun draw() { canvas.save() canvas.clear(Color.WHITE) canvas.setAlpha(48) canvas.setColor(Color.GREEN) canvas.drawRectangle(10.0, 10.0, 100.0, 100.0) canvas.setAlpha(255) // opaque for (i in 0 .. 5) { for (j in 0 .. 5) { canvas.setColor(Color(Math.floor(255-42.5*i), Math.floor(255-42.5*j), 0)) canvas.drawRectangle(150.0 + j*25.0, i*25.0, 25.0, 25.0) } } canvas.translate(x, y) canvas.setAlpha(128) canvas.rotate(alpha) canvas.setColor(Color.RED) canvas.setFont(32.0) canvas.drawText("Lovely", 0.0, 0.0) canvas.restore() if (animate) window.requestAnimationFrame { animate(it) } } fun animate(s: Double) { val delta = s - timeStamp timeStamp = s x += delta / 2.0 if (x > canvas.width) x = 0.0 alpha += 0.3 * delta if (alpha >= 360.0) alpha = 0.0 draw() } fun keyDown(e: Event) { val ek = e as KeyboardEvent var k = ek.key if (k === undefined) k = "${ek.keyCode.toChar().toLowerCase()}" when (k) { "a" -> x -= 3 "s" -> x += 3 "w" -> y -= 3 "z" -> y += 3 "j" -> alpha += 10.0 "k" -> alpha -= 10.0 "g" -> { if (!animate) timeStamp = window.performance.now() animate = !animate } else -> return } draw() e.preventDefault() } fun mouseDown(e: Event) { val em = e as MouseEvent x = em.offsetX y = em.offsetY draw() } } fun start() { println("Canvas3 starting...") println("Active keys are aswzjk and g for animation") Controller.draw() window.addEventListener("keydown", { Controller.keyDown(it) }, true) window.addEventListener("mousedown", { Controller.mouseDown(it) }, true) }
Now you can program your own web applications. Or try my implementation of the 2048 game. The movement keys are 'u', 'd', 'l', and 'r'.
While my JsCanvas class is convenient because you can use the same drawing code as for drawing to a bitmap and for the CS109 Android framework, you can of course remove the jscanvas.js file from your HTML file and use the Javascript functions for drawing to the canvas directly. See canvas-nojscanvas.kt for what the last version above would look like without JsCanvas. You may find the Mozilla documentation helpful: while it is meant for Javascript, much of the code works unchanged in Kotlin
Compiling to Javascript and running in the browser |