_updateOpacity() {
        if (!this.getMap()) { return; }

        const now = +new Date();
        let nextFrame = false;

        for (const key in this._tiles) {
            const tile = this._tiles[key];
            if (!tile.current || !tile.loaded) { continue; }

            const fade = Math.min(1, (now - tile.loaded) / 200);

            setOpacity(tile.el, fade);
            if (fade < 1) {
                nextFrame = true;
            } else {
                tile.active = true;
            }
        }

        if (nextFrame) {
            cancelAnimFrame(this._fadeFrame);
            this._fadeFrame = requestAnimFrame(this._updateOpacity.bind(this));
        }
    }
    _tileReady(err, tile) {
        if (!this.layer) {
            return;
        }
        if (err) {
            /**
             * tileerror event, fired when layer is 'dom' rendered and a tile errors
             *
             * @event TileLayer#tileerror
             * @type {Object}
             * @property {String} type - tileerror
             * @property {TileLayer} target - tile layer
             * @property {String} err  - error message
             * @property {Object} tile - tile
             */
            this.layer.fire('tileerror', {
                error: err,
                tile: tile
            });
        }

        tile.loaded = now();

        const map = this.getMap();

        if (this._fadeAnimated) {
            setOpacity(tile.el, 0);
            cancelAnimFrame(this._fadeFrame);
            this._fadeFrame = requestAnimFrame(this._updateOpacity.bind(this));
        } else {
            setOpacity(tile.el, 1);
            tile.active = true;
        }

        /**
         * tileload event, fired when layer is 'dom' rendered and a tile is loaded
         *
         * @event TileLayer#tileload
         * @type {Object}
         * @property {String} type - tileload
         * @property {TileLayer} target - tile layer
         * @property {Object} tile - tile
         */
        this.layer.fire('tileload', {
            tile: tile
        });

        if (this._noTilesToLoad()) {
            if (this._pruneTimeout) {
                clearTimeout(this._pruneTimeout);
            }
            if (map.isInteracting()) {
                this._pruneLevels();
            } else {
                this.layer.fire('layerload');
                const timeout = map ? map.options['zoomAnimationDuration'] : 250,
                    pruneLevels = (map && this.layer === map.getBaseLayer()) ? !map.options['zoomBackground'] : true;
                // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
                // to trigger a pruning.
                this._pruneTimeout = setTimeout(this._pruneTiles.bind(this, pruneLevels), timeout + 100);
            }
        }
    }