change worker functions to classes
diff --git a/player/js/worker_wrapper.js b/player/js/worker_wrapper.js
index bdd11c4..b6a628c 100644
--- a/player/js/worker_wrapper.js
+++ b/player/js/worker_wrapper.js
@@ -1,108 +1,259 @@
+/* eslint-disable max-classes-per-file */
 function workerContent() {
+  class ProxyElement {
+    constructor(type, namespace) {
+      this._state = 'init';
+      this._isDirty = false;
+      this._isProxy = true;
+      this._changedStyles = [];
+      this._changedAttributes = [];
+      this._changedElements = [];
+      this._textContent = null;
+      this.type = type;
+      this.namespace = namespace;
+      this.children = [];
+      localIdCounter += 1;
+      this.attributes = {
+        id: 'l_d_' + localIdCounter,
+      };
+      this.style = new Style(this);
+    }
+
+    appendChild(_child) {
+      _child.parentNode = this;
+      this.children.push(_child);
+      this._isDirty = true;
+      this._changedElements.push([_child, this.attributes.id]);
+    }
+
+    insertBefore(_newElement, _nextElement) {
+      var children = this.children;
+      for (var i = 0; i < children.length; i += 1) {
+        if (children[i] === _nextElement) {
+          children.splice(i, 0, _newElement);
+          this._isDirty = true;
+          this._changedElements.push([_newElement, this.attributes.id, _nextElement.attributes.id]);
+          return;
+        }
+      }
+      children.push(_nextElement);
+    }
+
+    setAttribute(_attribute, _value) {
+      this.attributes[_attribute] = _value;
+      if (!this._isDirty) {
+        this._isDirty = true;
+      }
+      this._changedAttributes.push(_attribute);
+    }
+
+    serialize() {
+      return {
+        type: this.type,
+        namespace: this.namespace,
+        style: this.style.serialize(),
+        attributes: this.attributes,
+        children: this.children.map(function (child) { return child.serialize(); }),
+        textContent: this._textContent,
+      };
+    }
+
+    // eslint-disable-next-line class-methods-use-this
+    addEventListener(_, _callback) {
+      setTimeout(_callback, 1);
+    }
+
+    setAttributeNS(_, _attribute, _value) {
+      this.attributes[_attribute] = _value;
+      if (!this._isDirty) {
+        this._isDirty = true;
+      }
+      this._changedAttributes.push(_attribute);
+    }
+
+    set textContent(_value) {
+      this._isDirty = true;
+      this._textContent = _value;
+    }
+  }
   var localIdCounter = 0;
   var animations = {};
 
   var styleProperties = ['width', 'height', 'display', 'transform', 'opacity', 'contentVisibility', 'mix-blend-mode'];
-  function createElement(namespace, type) {
-    var style = {
-      serialize: function () {
-        var obj = {};
-        for (var i = 0; i < styleProperties.length; i += 1) {
-          var propertyKey = styleProperties[i];
-          var keyName = '_' + propertyKey;
-          if (keyName in this) {
-            obj[propertyKey] = this[keyName];
-          }
+
+  function convertArguments(args) {
+    var arr = [];
+    var i;
+    var len = args.length;
+    for (i = 0; i < len; i += 1) {
+      arr.push(args[i]);
+    }
+    return arr;
+  }
+
+  function Style(element) {
+    this.element = element;
+  }
+  Style.prototype.serialize = function () {
+    var obj = {};
+    for (var i = 0; i < styleProperties.length; i += 1) {
+      var propertyKey = styleProperties[i];
+      var keyName = '_' + propertyKey;
+      if (keyName in this) {
+        obj[propertyKey] = this[keyName];
+      }
+    }
+    return obj;
+  };
+  styleProperties.forEach(function (propertyKey) {
+    Object.defineProperty(Style.prototype, propertyKey, {
+      set: function (value) {
+        if (!this.element._isDirty) {
+          this.element._isDirty = true;
         }
-        return obj;
+        this.element._changedStyles.push(propertyKey);
+        var keyName = '_' + propertyKey;
+        this[keyName] = value;
       },
+      get: function () {
+        var keyName = '_' + propertyKey;
+        return this[keyName];
+      },
+    });
+  });
+
+  class CanvasContext {
+    constructor(element) {
+      this.element = element;
+    }
+
+    createRadialGradient() {
+      function addColorStop() {
+        instruction.stops.push(convertArguments(arguments));
+      }
+      var instruction = {
+        t: 'rGradient',
+        a: convertArguments(arguments),
+        stops: [],
+      };
+      this.element.instructions.push(instruction);
+      return {
+        addColorStop: addColorStop,
+      };
+    }
+
+    createLinearGradient() {
+      function addColorStop() {
+        instruction.stops.push(convertArguments(arguments));
+      }
+      var instruction = {
+        t: 'lGradient',
+        a: convertArguments(arguments),
+        stops: [],
+      };
+      this.element.instructions.push(instruction);
+      return {
+        addColorStop: addColorStop,
+      };
+    }
+
+    get canvas() {
+      return this.element;
+    }
+  }
+
+  const canvasContextMethods = [
+    'fillRect',
+    'setTransform',
+    'drawImage',
+    'beginPath',
+    'moveTo',
+    'save',
+    'restore',
+    'fillText',
+    'setLineDash',
+    'clearRect',
+    'clip',
+    'rect',
+    'stroke',
+    'fill',
+    'closePath',
+    'bezierCurveTo',
+    'lineTo',
+  ];
+
+  canvasContextMethods.forEach(function (method) {
+    CanvasContext.prototype[method] = function () {
+      this.element.instructions.push({
+        t: method,
+        a: convertArguments(arguments),
+      });
     };
-    styleProperties.forEach(function (propertyKey) {
-      Object.defineProperty(style, propertyKey, {
-        set: function (value) {
-          if (!element._isDirty) {
-            element._isDirty = true;
-          }
-          element._changedStyles.push(propertyKey);
-          var keyName = '_' + propertyKey;
-          this[keyName] = value;
-        },
-        get: function () {
-          var keyName = '_' + propertyKey;
-          return this[keyName];
+  });
+
+  const canvasContextProperties = [
+    'globalAlpha',
+    'strokeStyle',
+    'fillStyle',
+    'lineCap',
+    'lineJoin',
+    'lineWidth',
+    'miterLimit',
+    'lineDashOffset',
+    'globalCompositeOperation',
+  ];
+
+  canvasContextProperties.forEach(function (property) {
+    Object.defineProperty(CanvasContext.prototype, property,
+      {
+        set: function (_value) {
+          this.element.instructions.push({
+            t: property,
+            a: _value,
+          });
         },
       });
-    });
-    localIdCounter += 1;
-    var element = {
-      _state: 'init',
-      _isDirty: false,
-      _changedStyles: [],
-      _changedAttributes: [],
-      _changedElements: [],
-      _textContent: null,
-      type: type,
-      namespace: namespace,
-      children: [],
-      attributes: {
-        id: 'l_d_' + localIdCounter,
-      },
-      style: style,
-      appendChild: function (child) {
-        child.parentNode = this;
-        this.children.push(child);
-        this._isDirty = true;
-        this._changedElements.push([child, this.attributes.id]);
-      },
-      insertBefore: function (newElement, nextElement) {
-        var children = this.children;
-        for (var i = 0; i < children.length; i += 1) {
-          if (children[i] === nextElement) {
-            children.splice(i, 0, newElement);
-            this._isDirty = true;
-            this._changedElements.push([newElement, this.attributes.id, nextElement.attributes.id]);
-            return;
-          }
-        }
-        children.push(nextElement);
-      },
-      setAttribute: function (attribute, value) {
-        this.attributes[attribute] = value;
-        if (!element._isDirty) {
-          element._isDirty = true;
-        }
-        element._changedAttributes.push(attribute);
-      },
-      serialize: function () {
-        return {
-          type: this.type,
-          namespace: this.namespace,
-          style: this.style.serialize(),
-          attributes: this.attributes,
-          children: this.children.map(function (child) { return child.serialize(); }),
-          textContent: this._textContent,
-        };
-      },
-      getContext: function () { return { fillRect: function () {}, drawImage: function () {} }; },
-      addEventListener: function (_, callback) {
-        setTimeout(callback, 1);
-      },
-      setAttributeNS: function (_, attribute, value) {
-        this.attributes[attribute] = value;
-        if (!element._isDirty) {
-          element._isDirty = true;
-        }
-        element._changedAttributes.push(attribute);
-      },
-    };
-    element.style = style;
-    Object.defineProperty(element, 'textContent', {
-      set: function (value) {
-        element._isDirty = true;
-        element._textContent = value;
-      },
-    });
-    return element;
+  });
+
+  class CanvasElement extends ProxyElement {
+    constructor(type, namespace) {
+      super(type, namespace);
+      this.instructions = [];
+      this._width = 0;
+      this._height = 0;
+      this.context = new CanvasContext(this);
+    }
+
+    set width(_value) {
+      this._width = _value;
+    }
+
+    set height(_value) {
+      this._height = _value;
+    }
+
+    get width() {
+      return this._width;
+    }
+
+    get height() {
+      return this._height;
+    }
+
+    getContext() {
+      return this.context;
+    }
+
+    resetInstructions() {
+      this.instructions.length = 0;
+    }
+  }
+
+  function createElement(namespace, type) {
+    if (type === 'canvas') {
+      return new CanvasElement(type, namespace);
+    }
+    return new ProxyElement(type, namespace);
   }
 
   var window = self; // eslint-disable-line no-redeclare, no-unused-vars
@@ -177,11 +328,17 @@
       var wrapper;
       var animation;
       var elements = [];
+      var canvas;
       if (params.renderer === 'svg') {
         wrapper = document.createElement('div');
         params.container = wrapper;
       } else {
-        var canvas = params.rendererSettings.canvas;
+        canvas = params.rendererSettings.canvas;
+        if (!canvas) {
+          canvas = document.createElement('canvas');
+          canvas.width = params.animationData.w;
+          canvas.height = params.animationData.h;
+        }
         var ctx = canvas.getContext('2d');
         params.rendererSettings.context = ctx;
       }
@@ -250,6 +407,18 @@
             },
           });
         });
+      } else if (canvas._isProxy) {
+        animation.addEventListener('drawnFrame', function (event) {
+          self.postMessage({
+            type: 'CanvasUpdated',
+            payload: {
+              instructions: canvas.instructions,
+              id: payload.id,
+              currentTime: event.currentTime,
+            },
+          });
+          canvas.resetInstructions();
+        });
       }
       animation.addEventListener('DOMLoaded', function () {
         self.postMessage({
@@ -360,7 +529,7 @@
   return new Worker(url);
 }
 // eslint-disable-next-line no-unused-vars
-var lottie = (function () {
+const lottie = (function () {
   'use strict';
 
   var workerInstance = createWorker(workerContent);
@@ -490,6 +659,40 @@
     }
   }
 
+  function createInstructionsHandler(canvas) {
+    var ctx = canvas.getContext('2d');
+    var map = {
+      beginPath: ctx.beginPath,
+      closePath: ctx.closePath,
+      rect: ctx.rect,
+      clip: ctx.clip,
+      clearRect: ctx.clearRect,
+      setTransform: ctx.setTransform,
+      moveTo: ctx.moveTo,
+      bezierCurveTo: ctx.bezierCurveTo,
+      lineTo: ctx.lineTo,
+      fill: ctx.fill,
+      save: ctx.save,
+      restore: ctx.restore,
+    };
+    return function (instructions) {
+      for (let i = 0; i < instructions.length; i += 1) {
+        var instruction = instructions[i];
+        var fn = map[instruction.t];
+        if (fn) {
+          fn.apply(ctx, instruction.a);
+        } else {
+          ctx[instruction.t] = instruction.a;
+        }
+      }
+    };
+  }
+
+  function handleCanvasAnimationUpdate(payload) {
+    var animation = animations[payload.id];
+    animation.instructionsHandler(payload.instructions);
+  }
+
   function handleEvent(payload) {
     var animation = animations[payload.id];
     if (animation) {
@@ -518,6 +721,7 @@
     DOMLoaded: handleAnimationLoaded,
     SVGloaded: handleSVGLoaded,
     SVGupdated: handleAnimationUpdate,
+    CanvasUpdated: handleCanvasAnimationUpdate,
     event: handleEvent,
     playing: handlePlaying,
     paused: handlePaused,
@@ -761,9 +965,17 @@
           }
 
           // Transfer control to offscreen if it's not already
-          var offscreen = canvas instanceof OffscreenCanvas ? canvas : canvas.transferControlToOffscreen();
-          animationParams.rendererSettings.canvas = offscreen;
-          transferedObjects.push(animationParams.rendererSettings.canvas);
+          var transferCanvas = canvas;
+          if ((typeof OffscreenCanvas !== 'undefined')
+            && !(canvas instanceof OffscreenCanvas)
+            && canvas.transferControlToOffscreen) {
+            transferCanvas = canvas.transferControlToOffscreen();
+            animationParams.rendererSettings.canvas = transferCanvas;
+            transferedObjects.push(transferCanvas);
+          } else {
+            animation.canvas = canvas;
+            animation.instructionsHandler = createInstructionsHandler(canvas);
+          }
         }
         animations[animationId] = animation;
         workerInstance.postMessage({