Here's my roller door simulation - which is part of the flow I used to control my garage door via Alexa.
In the function node I use a counter that increments from 0 to 100 or decrements from 100 to 0.
You might be able to modify it to suit your needs.
[{"id":"a47754ee.cc9948","type":"ui_template","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","group":"273505a6.34ddaa","name":"Roller door simulation","order":1,"width":"0","height":"0","format":"<style>\n :root {\n --rollade-color: #097479;\n --rollade-width: 90px;\n }\n .rollade__container {\n width: var(--rollade-width);\n overflow: hidden;\n position: relative;\n }\n .rollade__fenster {\n position: relative;\n overflow: hidden;\n z-index: 1;\n }\n .rollade__fenster path {\n stroke: var(--rollade-color);\n fill:none!important;\n stroke-width:6px;\n }\n .rollade__rollade {\n position: absolute;\n width: 100%;\n height: 100%;\n left: 0px;\n z-index: 10;\n transition: all 1s;\n }\n .rollade__rollade path.line {\n stroke: white;\n fill: none;\n stroke-width: 1.5px;\n }\n .rollade__rollade path.bg {\n fill: var(--rollade-color);\n }\n \n </style>\n\n <h3>Door position</h3>\n <div class=\"rollade__container\">\n <svg class=\"rollade__fenster\" width=\"100%\" height=\"100%\" viewBox=\"0 0 170 188\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:space=\"preserve\" xmlns:serif=\"http://www.serif.com/\" style=\"fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;\">\n <g transform=\"matrix(1,0,0,1,-15.909,-45.6212)\">\n <g transform=\"matrix(0.989904,0,0,0.992491,4.03539,0.837446)\">\n <path d=\"M180.329,56.389C180.329,51.839 176.626,48.145 172.064,48.145L23.291,48.145C18.729,48.145 15.025,51.839 15.025,56.389L15.025,222.454C15.025,227.004 18.729,230.698 23.291,230.698L172.064,230.698C176.626,230.698 180.329,227.004 180.329,222.454L180.329,56.389Z\" />\n </g>\n <g transform=\"matrix(1,0,0,1.38253,3.04599,-37.9811)\">\n <path d=\"M97.589,64.432L97.589,191.286\" />\n </g>\n <g transform=\"matrix(1,0,0,1,2.84135,56.1777)\">\n <path d=\"M18.74,83.189L177.463,84.189\" />\n </g>\n </g>\n </svg>\n <svg class=\"rollade__rollade\" style=\"top: calc((-1) * {{msg.payload}}%);\" width=\"100%\" height=\"100%\" viewBox=\"0 0 181 190\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:space=\"preserve\" xmlns:serif=\"http://www.serif.com/\" style=\"fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;\">\n <g transform=\"matrix(1.35281,0,0,1.35281,-45.3041,-102.109)\">\n <path class=\"bg\" d=\"M167.134,82.161C167.134,78.473 164.14,75.479 160.452,75.479L40.171,75.479C36.483,75.479 33.489,78.473 33.489,82.161L33.489,208.93C33.489,212.618 36.483,215.612 40.171,215.612L160.452,215.612C164.14,215.612 167.134,212.618 167.134,208.93L167.134,82.161Z\"/>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,-9)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,1)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,11)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,21)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,31)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,41)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,51)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,61)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,71)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,81)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,91)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,101)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n <g transform=\"matrix(1.69574,0,0,1,18.3547,111)\">\n <path class=\"line\" d=\"M13.383,95.652L84.46,95.652\" />\n </g>\n </g>\n </svg>\n </div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","className":"","x":1460,"y":200,"wires":[[]]},{"id":"8c54390f2bb17c9b","type":"inject","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"date","x":1030,"y":140,"wires":[["bb3c87874a48df16"]]},{"id":"bb3c87874a48df16","type":"trigger","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","name":"","op1":"1","op2":"0","op1type":"str","op2type":"str","duration":"250","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":1040,"y":200,"wires":[["c77fdce872c5e850","2c9ca19a07baabd2"]]},{"id":"c77fdce872c5e850","type":"debug","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1250,"y":260,"wires":[]},{"id":"6441625a23250da7","type":"link out","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","name":"","mode":"link","links":["825f1f9b12488582"],"x":995,"y":280,"wires":[]},{"id":"825f1f9b12488582","type":"link in","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","name":"","links":["6441625a23250da7"],"x":1095,"y":280,"wires":[["bb3c87874a48df16"]]},{"id":"2c9ca19a07baabd2","type":"function","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","name":"","func":"var roller_pos = flow.get(\"roller_pos\");\n\nvar state_register = flow.get(\"state_register\") || \"S0\";\n\nif (state_register == \"S0\") {\n flow.set(\"roller_pos\",0);\n node.send( [{payload: 0}] );\n}\nelse if (state_register == \"S1\") {\n if (roller_pos < 100){\n roller_pos++;\n flow.set(\"roller_pos\", roller_pos);\n }\n node.send( [{payload: roller_pos}] );\n}\n\nelse if (state_register == \"S2\") {\n flow.set(\"roller_pos\", 100);\n node.send( [{payload: 100}] );\n }\n\nelse if (state_register == \"S3\") {\n if (roller_pos > 1){\n roller_pos--;\n flow.set(\"roller_pos\", roller_pos);\n }\n node.send( [{payload: roller_pos}] );\n}\n\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1240,"y":200,"wires":[["a47754ee.cc9948"]]},{"id":"1e034ec8bb4788f4","type":"comment","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","name":"Roller door simulation - nothing to do with controlling the door","info":"","x":1160,"y":60,"wires":[]},{"id":"eb17e5b36d1b92f5","type":"comment","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","name":"Dave's garage door takes 25-second to open or close","info":"","x":1140,"y":100,"wires":[]},{"id":"bbbccb50d0c6ccb6","type":"comment","z":"fc2ffed5c7a37e1d","g":"3f33ed654be29500","name":"Roller door simulation has 100 steps, so each step is 250ms","info":"","x":1360,"y":140,"wires":[]},{"id":"273505a6.34ddaa","type":"ui_group","name":"Garage roller door","tab":"fc0f45ae.d6066","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"fc0f45ae.d6066","type":"ui_tab","name":"Garage roller door","icon":"dashboard","order":45,"disabled":false,"hidden":false}]