Search code examples
pythonrapydscript

Simple image rotation with Python and DOM using RapydScript


I have this code write in Python and works fine with Brython. This code rotate image in this case a cog. How Can I change it, and what change to work with RapydScript? I am new at programming so please have patience :D

<!DOCTYPE html>
<html>
<head>

<!-- load Brython -->
<script src="http://brython.info/src/brython_dist.js"></script>


<!-- the main script; after loading, Brython will run all 'text/python3' scripts -->
<script type='text/python'>
from browser import window, timer, document, html
import time

<!-- I know that here, I must use this t0 = Date.now() -->

t0 = time.time()


def user_agent():
   """ Helper function for determining the user agent """
   if window.navigator.userAgent.find('Chrome'):
       return 'chrome'
   elif window.navigator.userAgent.find('Firefox'):
       return 'firefox'
   elif window.navigator.userAgent.find('MSIE'):
       return 'msie'
   elif window.navigator.userAgent.find('Opera'):
       return 'opera'

# Dict Mapping UserAgents to Transform Property names
rotate_property = {
   'chrome':'WebkitTransform',
   'firefox':'MozTransform',
   'msie':'msTransform',
   'opera':'OTransform'
}

degrees = 0
def animation_step(elem_id):
   """ Called every 30msec to increase the rotatation of the element. """
   global degrees, tm

   # Get the right property name according to the useragent
   agent = user_agent()
   prop = rotate_property.get(agent,'transform')

   # Get the element by id
   el = document[elem_id]

   # Set the rotation of the element
   setattr(el.style, prop, "rotate("+str(degrees)+"deg)")
   document['status'].innerHTML = "rotate("+str(degrees)+" deg)"


   # Increase the rotation
   degrees += 1
   if degrees > 360:
       # Stops the animation after 360 steps
       timer.clear_interval(tm)
       degrees = 0

# Start the animation
tm = timer.set_interval(lambda id='img1':animation_step(id),30)

document['status3'].innerHTML = "Time of execution python code("+str(time.time()-t0)+" ms)"

<!-- I know that here i must use this: "Time of execution python code", Date.now()-t0, "ms") -->
</script>

</head>

<!-- After the page has finished loading, run bootstrap Brython by running
     the Brython function. The argument '1' tells Brython to print error
     messages to the console. -->
<body onload='brython(1)'>

<img id="img1" src="cog1.png" alt="cog1">
<script>animation_step("img1",30);</script>
<h2 style="width:200px;" id="status"></h2>
<h2 style="width:800px;" id="status3"></h2>


</body>  
</html>

Solution

  • I'm not too familiar with Brython, but right away I can tell you that to port it to RapydScript you simply need to drop most of the unnecessary abstractions that I see the code importing, since RapydScript is closer to native JavaScript. As far as having the RapydScript code in the browser, you have a couple options:

    • (recommended) compile your code ahead of time and include the .js file in your html (similar to Babel, UglifyJS, etc.), this way the code will run much faster and won't require you to include the compiler in the page
    • use an in-browser RapydScript compiler (this one seems to be the most up-to-date if you don't want to tinker with compilation: https://github.com/adousen/RapydScript-pyjTransformer) and include the code inside <script type="text/pyj"> tag similar to what you have done in the example.

    Now, assuming you pick the recommended option above, the next step is to drop the Brython boilerplate from the code, here is what your logic would look like in RapydScript (note that I also refactored it a little, removing the unnecessary 2-stage rotate method resolution and unneeded lambda call):

    t0 = Date.now()
    
    def rotate_property():
       """ Helper function mapping user agents to transform proeprty names """
       if 'Chrome' in window.navigator.userAgent: return 'webkitTransform'
       elif 'Firefox' in window.navigator.userAgent: return 'MozTransform'
       elif 'MSIE' in window.navigator.userAgent: return 'msTransform'
       elif 'Opera' in window.navigator.userAgent: return 'OTransform'
       return 'transform'
    
    degrees = 0
    def animation_step(elem_id='img1'):
       """ Called every 30msec to increase the rotatation of the element. """
       nonlocal degrees
    
       # Get the right property name according to the useragent
       prop = rotate_property()
    
       # Get the element by id
       el = document.getElementById(elem_id)
    
       # Set the rotation of the element
       el.style[prop] = "rotate(" + degrees + "deg)"
       document.getElementById('status').innerHTML = "rotate(" + degrees + "deg)"
    
       # Increase the rotation
       degrees += 1
       if degrees > 360:
           # Stops the animation after 360 steps
           clearInterval(tm)
           degrees = 0
    
    # Start the animation
    tm = setInterval(animation_step, 30)
    document.getElementById('status3').innerHTML = "Time of execution python code(" + (Date.now() - t0) + " ms)"
    

    A few things to note:

    • imports are no longer needed, RapydScript doesn't need boilerplate to interact with JavaScript
    • Pythonic timer.set_interval and timer.clear_interval have been replaced with JavaScript equivalents (setInterval and clearInterval)
    • document you see in my code is the DOM itself, document you have in Brython code is a wrapper around it, hence accessing it works a bit differently
    • RapydScript dropped global a long time ago in favor of Python 3's safer nonlocal, which is what I used in the code instead
    • instead of time module, RapydScript has direct access to JavaScript's Date class, which I used in the code to time it
    • I would also recommend moving prop = rotate_property() call outside the function, since the user agent won't change between function calls (in this case the operation is relatively cheap, but for more complex logic this will improve your performance)
    • you seem to be launching Brython from HTML via body onload, get rid of that as well as the line that says <script>animation_step("img1",30);</script>, the above code should trigger for you automatically as soon as page loads courtesy of setInterval call
    • since RapydScript uses unicode characters to avoid name collisions, you need to tell HTML to treat the document as unicode by adding this line to head: <meta charset="UTF-8">
    • for future reference, none of your onload calls to RapydScript will work, because unlike Brython, RapydScript protects itself in its own scope, invisible to the outside (this has been the accepted practice in JavaScript world for a long time), your options for making onload work are:
      • (not recommended) compile the file with -b flag to omit the self-protecting scope
      • (recommended) inside your code, attach your functions to the global window object if you want them accessible from outside

    Your actual html code that calls the compiled version of the above code would then look like this:

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    </head>
    <body>
    <img id="img1" src="cog1.png" alt="cog1">
    <h2 style="width:200px;" id="status"></h2>
    <h2 style="width:800px;" id="status3"></h2>
    <script src="myfile.js"></script>
    </body>
    </html>