Search code examples
javascriptcsssafari

Why updating brightness filter has not effect in Safari?


I'm updating DOM element's brightness/contrast from JavaScript:

let style = `brightness(${brightness}%) contrast(${contrast}%)`;
element.style.filter = style;

This happens when the user holds left mouse button and moves cursor. However, the brightness/contrast does NOT change in Safari (on Mac) unless the scroll bars are visible. However, the brightness does change in other browsers (Chrome, Firefox, Edge). Is it a bug in Safari? How do I make it work in Safari?

Demo: https://l36vng.csb.app/

Code: https://codesandbox.io/s/charming-vaughan-l36vng?file=/main.js:2207-2306

PS. This also does not work in Safari on iOS (if I include touch event handlers to the code above). My Mac Safari version is 16.1.


Solution

  • This seems to be fixed in the latest Technology Preview (Release 160 (Safari 16.4, WebKit 18615.1.14.3)).

    For the time being, you can workaround the issue by adding a super fast transition on the filter property, this will make current stable happy and update the filter as expected.

    // Change brightness/contrast of the image by holding
    // left mouse button on the image and moving cursor.
    // Moving horizontally changes brightness,
    // moving vertically changes contrast.
    class Brightness {
      constructor(element) {
        this.element = element;
    
        // x and y mouse coordinates of the cursor
        // when the brightness change was started
        this.startPosition = null;
    
        this.brightnessPercent = 100;
    
        // Current brightness/contrast percentage.
        this.currentAmount = {
          brightness: 100,
          contrast: 100
        };
    
        // True if the user holds left mouse bottom, which
        // indicates that brightness is being changed.
        // False by default or when the left mouse button is released.
        this.active = false;
    
        this.addEventListeners();
      }
    
      addEventListeners() {
        let element = this.element;
    
        // Start changing brightness
        // -----------------
    
        element.addEventListener("mousedown", (e) => {
          if (e.button !== 0) return; // Not a left mouse button
          this.active = true;
          e.preventDefault();
          this.startChange(e);
        });
    
        // End changing brightness
        // -----------------
    
        document.addEventListener("mouseup", () => {
          this.active = false;
        });
    
        // Drag the pointer over the bar
        // -----------------
    
        document.addEventListener("mousemove", (e) => {
          if (!this.active) return;
          this.change(e);
        });
      }
    
      startChange(e) {
        this.startPosition = this.positionFromCursor(e);
      }
    
      change(e) {
        let position = this.positionFromCursor(e);
    
        let xChange = position.x - this.startPosition.x;
        this.changeCurrentAmount(xChange, "brightness");
    
        let yChange = position.y - this.startPosition.y;
        this.changeCurrentAmount(yChange, "contrast");
    
        this.changeImageStyle();
      }
    
      changeCurrentAmount(change, type) {
        change /= 2; // Decrease rate of change
        change = Math.round(change);
        let amount = 100 + change;
        if (type === "contrast") amount = 100 - change;
        if (amount < 0) amount = 0;
        this.currentAmount[type] = amount;
      }
    
      changeImageStyle() {
        let brightness = this.currentAmount.brightness;
        let contrast = this.currentAmount.contrast;
        let css = `brightness(${brightness}%) contrast(${contrast}%)`;
        this.element.style.filter = css;
      }
    
      positionFromCursor(e) {
        let pointerX = e.pageX;
        let pointerY = e.pageY;
    
        if (e.touches && e.touches.length > 0) {
          pointerX = e.touches[0].pageX;
          pointerY = e.touches[0].pageY;
        }
    
        return { x: pointerX, y: pointerY };
      }
    }
    
    document.addEventListener("DOMContentLoaded", function () {
      const imageElement = document.querySelector(".Image");
      new Brightness(imageElement);
    });
    .Image {
      /* Safari bug workaround */
      transition: filter 1e-6s linear;
    }
    <img
        class="Image"
        width="512"
        height="512"
        src="https://64.media.tumblr.com/df185589006321d70d37d1a59040d7c3/df57f19b4b4b783a-cb/s250x400/e76347d6b1cb4aa08839036fb0407a9f6108a0ab.jpg"
      />