]> git.r.bdr.sh - rbdr/heart/blobdiff - js/lib/heart_renderer.js
Remove unnecessary log
[rbdr/heart] / js / lib / heart_renderer.js
index f6ece8630d57386ca19c165cdd67fe481d16c2ba..356ed72d94d0c64456271ac0d494b461dda406dc 100644 (file)
@@ -3,9 +3,6 @@
 ((window) => {
 
   const kColorIteratorLimit = 256;
-  const kRedSpeed = 0.1;
-  const kGreenSpeed = 0.2;
-  const kBlueSpeed = 0.15;
 
   /**
    * Renders a Heart, has its own canvas, which will be placed in the element
        * @instance
        * @name canvas
        * @type HTMLCanvasElement
-       * @default A brand new full width and height canvas
+       * @default A brand new canvas
        */
       this.canvas = window.document.createElement('canvas');
-      this.canvas.style.height = '100%';
-      this.canvas.style.width = '100%';
 
       /**
        * The maximum fps that will be used
        */
       this.fps = 60;
 
+      /**
+       * The size of the heart as a percentage of the canvas smallest dimension
+       *
+       * @memberof HeartRenderer
+       * @instance
+       * @name heartSize
+       * @type Number
+       * @default 40
+       */
+      this.heartSize = 40;
+
+      /**
+       * The max size of the heart as a percentage of the canvas smallest dimension
+       *
+       * @memberof HeartRenderer
+       * @instance
+       * @name maxHeartSize
+       * @type Number
+       * @default 75
+       */
+      this.maxHeartSize = 75;
+
+      /**
+       * The min size of the heart as a percentage of the canvas smallest dimension
+       *
+       * @memberof HeartRenderer
+       * @instance
+       * @name minHeartSize
+       * @type Number
+       * @default 10
+       */
+      this.minHeartSize = 10;
+
+      this._ticking = false; // Lock for wheel event.
+      this._resizeMagnitude = 0.1; // Multiplies the wheel delta to resize the heart
+      this._resizeSpeed = 1; // How many percent points per frame we'll resize to match target
+      this._trackingSpeed = 10; // How many pixels per frame will we move to match target
+      this._following = null; // The status of mouse follow.
+      this._center = null; // The actual center
+      this._targetHeartSize = this.heartSize;
+      this._
+      this._targetCenter = {
+        x: 0,
+        y: 0
+      }; // the target coordinates
       this._animating = false; // The status of the animation.
       this._previousFrameTime = Date.now(); // The timestamp of the last frame for fps control
+      this._cursorTimeout = 500; // Timeout to hide the cursor in milliseconds
       this._currentColor = { // The current color that will be painted
         red: 100,
         blue: 0,
         green: 50
       };
+      this._colorSpeed = {
+        red: 0.1,
+        blue: 0.2,
+        green: 0.15
+      };
+      this._colorDirection = {
+        red: 1,
+        blue: 1,
+        green: 1
+      };
+
+      this._detectWheel();
+      this.startFollowingMouse();
 
       Object.assign(this, config);
     }
     render(element) {
 
       element.appendChild(this.canvas);
+
+      this._targetCenter = {
+        x: Math.round(this.canvas.width / 2),
+        y: Math.round(this.canvas.height / 2)
+      }; // the target coordinates
+      this.resize();
+    }
+
+    /**
+     * Resizes the canvas
+     *
+     * @memberof HeartRenderer
+     * @function render
+     * @instance
+     */
+    resize() {
+
+      if (this.canvas.parentElement) {
+        this.canvas.width = this.canvas.parentElement.offsetWidth;
+        this.canvas.height = this.canvas.parentElement.offsetHeight;
+      }
+    }
+
+    /**
+     * Follows the mouse
+     *
+     * @memberof HeartRenderer
+     * @function startFollowingMouse
+     * @instance
+     */
+    startFollowingMouse() {
+
+      if (!this._following) {
+        this._following = this._setCenterFromMouse.bind(this);
+        this.canvas.addEventListener('mousemove', this._following);
+      }
+    }
+
+    /**
+     * Stop following the mouse
+     *
+     * @memberof HeartRenderer
+     * @function stopFollowingMouse
+     * @instance
+     */
+    stopFollowingMouse() {
+
+      if (this._following) {
+        this.canvas.removeEventListener('mouseover', this._following);
+        this._following = null;
+        this._targetCenter = {
+          x: Math.round(this.canvas.width / 2),
+          y: Math.round(this.canvas.height / 2)
+        }; // the target coordinates
+      }
     }
 
     /**
     // The actual animation processing function.
     _animateStep(context, delta) {
 
-      this._currentColor.red = Math.round(this._currentColor.red + delta * kRedSpeed) % kColorIteratorLimit;
-      this._currentColor.green = Math.round(this._currentColor.green + delta * kGreenSpeed) % kColorIteratorLimit;
-      this._currentColor.blue = Math.round(this._currentColor.blue + delta * kBlueSpeed) % kColorIteratorLimit;
+      this._updateColor(delta);
+      this._drawHeart(context, delta);
+    }
+
+    // Updates the current color
+    _updateColor(delta) {
+
+      const red = this._updateColorComponent('red', delta);
+      const green = this._updateColorComponent('green', delta);
+      const blue = this._updateColorComponent('blue', delta);
+
+      this._currentColor.red = red;
+      this._currentColor.green = green;
+      this._currentColor.blue = blue;
+    }
+
+    // Updates a single color component.
+    _updateColorComponent(component, delta) {
+      let color = Math.round(this._currentColor[component] + (delta * this._colorSpeed[component] * this._colorDirection[component]));
+      if (color >= kColorIteratorLimit) {
+        this._colorDirection[component] = -1;
+        color = kColorIteratorLimit;
+      }
+
+      if (color <= 0) {
+        this._colorDirection[component] = 1;
+        color = 0;
+      }
+
+      return color;
+    }
+
+    // Draws a heart
+    _drawHeart(context, delta) {
+
+      const canvasHeight = this.canvas.height;
+      const canvasWidth = this.canvas.width;
+
+      const referenceDimension = canvasWidth < canvasHeight ? canvasWidth : canvasHeight;
+
+      this.heartSize += Math.sign(this._targetHeartSize - this.heartSize) * this._resizeSpeed;
+
+      const heartSize = Math.round(referenceDimension * this.heartSize * .01);
+      const radius = heartSize / 2;
+
+      if (!this._center) {
+        this._center = {};
+        this._center.x = Math.round(canvasWidth / 2);
+        this._center.y = Math.round(canvasHeight / 2);
+      }
+
+      const deltaY = this._targetCenter.y - this._center.y;
+      const deltaX = this._targetCenter.x - this._center.x;
+      const angle = Math.atan2(deltaY, deltaX);
+
+      // Move towards the target
+      this._center.x += Math.cos(angle) * this._trackingSpeed;
+      this._center.y += Math.sin(angle) * this._trackingSpeed;
+
+      const canvasCenterX = this._center.x;
+      const canvasCenterY = this._center.y;
+      const centerX = -radius;
+      const centerY = -radius;
+
+
+      // translate and rotate, adjusting for weight of the heart.
+      context.translate(canvasCenterX, canvasCenterY + radius / 4);
+      context.rotate(-45 * Math.PI / 180);
 
+      // Fill the ventricles of the heart
       context.fillStyle = `rgb(${this._currentColor.red}, ${this._currentColor.green}, ${this._currentColor.blue})`;
-      context.fillRect(0, 0, this.canvas.scrollWidth, this.canvas.scrollHeight);
+      context.fillRect(centerX, centerY, heartSize, heartSize);
+
+      // Left atrium
+      context.beginPath();
+      context.arc(centerX + radius, centerY, radius, 0, 2 * Math.PI, false);
+      context.fill();
+      context.closePath();
+
+      // Right atrium
+      context.beginPath();
+      context.arc(centerX + heartSize, centerY + radius, radius, 0, 2 * Math.PI, false);
+      context.fill();
+      context.closePath();
+
+      context.setTransform(1, 0, 0, 1, 0, 0);
+    }
+
+    // Sets the center from mouse
+    _setCenterFromMouse(event) {
+
+      this._showCursor();
+      this._targetCenter.x = event.offsetX;
+      this._targetCenter.y = event.offsetY;
+      setTimeout(this._hideCursor.bind(this), this._cursorTimeout);
+    }
+
+    // Binds the wheel event to resize the heart
+    _detectWheel() {
+
+      this.canvas.addEventListener('wheel', this._onWheel.bind(this));
+    }
+
+    // Handle the mouse wheel movement
+    _onWheel(event) {
+
+      if (!this._ticking) {
+        this._ticking = true;
+        window.requestAnimationFrame(this._resizeHeartFromDelta.bind(this, event.deltaY));
+      }
+    }
+
+    // Use delta to resize the heart
+    _resizeHeartFromDelta(delta) {
+
+      let heartSize = this.heartSize + (this._resizeMagnitude * delta);
+
+      if (heartSize > this.maxHeartSize) {
+        heartSize = this.maxHeartSize;
+      }
+
+      if (heartSize < this.minHeartSize) {
+        heartSize = this.minHeartSize;
+      }
+
+      this._targetHeartSize = heartSize;
+      this._ticking = false;
+    }
+
+    // Apply a class to show the cursor.
+    _showCursor() {
+      this.canvas.classList.add('mouse-moving');
+    }
+
+    // Remove class to hide the cursor.
+    _hideCursor() {
+      this.canvas.classList.remove('mouse-moving');
     }
   };