2017-07-25 69 views
16

Wykonuję Kalejdoskop na mojej stronie. Wszystko, co robi, to zrobić zdjęcie (za pomocą Drag & Drop lub domyślny obraz przy załadunku) i skopiować go 10 razy (po jednym dla każdego plasterka kalejdoskopu). Przy ruchu myszką, obrót i skala plasterków są regulowane w celu uzyskania pożądanego efektu.Rysunek na płótnie zajmuje dużo czasu w Safari, ale nie w Chrome ani w FF

W Google Chrome i Firefox działa bezproblemowo, bez opóźnień. Jednak w Safari strona internetowa nie nadaje się do użytku, ponieważ jest zbyt wolna. Czy czegoś brakuje?

Oto JSFiddle pokazujący problem. Uwaga! Próbowałem już zastąpić setTimeout (aktualizacja, 1000/60) przez RequestAnimationFrame, bez żadnych ulepszeń.

JSFiddle: Link

$(document).ready(function() { 
    //SCRIPT KALEIDOSCOPE BASE 

    var DragDrop, Kaleidoscope, c, dragger, gui, i, image, kaleidoscope, len, onChange, onMouseMoved, options, ref, tr, tx, ty, update, 
     bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 

    Kaleidoscope = (function() { 
     Kaleidoscope.prototype.HALF_PI = Math.PI/2; 

     Kaleidoscope.prototype.TWO_PI = Math.PI * 2; 

     var optimal_radius = window.innerHeight; 

     if (window.innerWidth > optimal_radius) { 
      optimal_radius = window.innerWidth; 
     } 

     function Kaleidoscope(options1) { 
      var key, ref, ref1, val; 
      this.options = options1 != null ? options1 : {}; 
      this.defaults = { 
       offsetRotation: 0.0, 
       offsetScale: 1.0, 
       offsetX: 0.0, 
       offsetY: 0.0, 
       radius: optimal_radius/1.4, 
       slices: 12, 
       zoom: 1.0 
      }; 
      ref = this.defaults; 
      for (key in ref) { 
       val = ref[key]; 
       this[key] = val; 
      } 
      ref1 = this.options; 
      for (key in ref1) { 
       val = ref1[key]; 
       this[key] = val; 
      } 
      if (this.domElement == null) { 
       this.domElement = document.getElementById('kaleidoscope'); 
      } 
      if (this.context == null) { 
       this.context = this.domElement.getContext('2d'); 
      } 
      if (this.image == null) { 
       this.image = document.createElement('img'); 
      } 
     } 

     Kaleidoscope.prototype.draw = function() { 
      var cx, i, index, ref, results, scale, step; 
      this.domElement.width = this.domElement.height = this.radius * 2; 
      this.context.fillStyle = this.context.createPattern(this.image, 'repeat'); 
      scale = this.zoom * (this.radius/Math.min(this.image.width, this.image.height)); 
      step = this.TWO_PI/this.slices; 
      cx = this.image.width/2; 
      results = []; 
      for (index = i = 0, ref = this.slices; 0 <= ref ? i <= ref : i >= ref; index = 0 <= ref ? ++i : --i) { 
       this.context.save(); 
       this.context.translate(this.radius, this.radius); 
       this.context.rotate(index * step); 
       this.context.beginPath(); 
       this.context.moveTo(-0.5, -0.5); 
       this.context.arc(0, 0, this.radius, step * -0.51, step * 0.51); 
       this.context.lineTo(0.5, 0.5); 
       this.context.closePath(); 
       this.context.rotate(this.HALF_PI); 
       this.context.scale(scale, scale); 
       this.context.scale([-1, 1][index % 2], 1); 
       this.context.translate(this.offsetX - cx, this.offsetY); 
       this.context.rotate(this.offsetRotation); 
       this.context.scale(this.offsetScale, this.offsetScale); 
       this.context.fill(); 
       results.push(this.context.restore()); 
      } 
      return results; 
     }; 

     return Kaleidoscope; 

    })(); 

    DragDrop = (function() { 
     function DragDrop(callback, context, filter) { 
      var disable; 
      this.callback = callback; 
      this.context = context != null ? context : document; 
      this.filter = filter != null ? filter : /^image/i; 
      this.onDrop = bind(this.onDrop, this); 
      disable = function(event) { 
       event.stopPropagation(); 
       return event.preventDefault(); 
      }; 
      this.context.addEventListener('dragleave', disable); 
      this.context.addEventListener('dragenter', disable); 
      this.context.addEventListener('dragover', disable); 
      this.context.addEventListener('drop', this.onDrop, false); 
     } 

     DragDrop.prototype.onDrop = function(event) { 
      var file, reader; 
      event.stopPropagation(); 
      event.preventDefault(); 
      file = event.dataTransfer.files[0]; 
      if (this.filter.test(file.type)) { 
       reader = new FileReader; 
       reader.onload = (function(_this) { 
        return function(event) { 
         return typeof _this.callback === "function" ? _this.callback(event.target.result) : void 0; 
        }; 
       })(this); 
       return reader.readAsDataURL(file); 
      } 
     }; 

     return DragDrop; 

    })(); 

    image = new Image; 

    image.onload = (function(_this) { 
     return function() { 
      return kaleidoscope.draw(); 
     }; 
    })(this); 

    image.src = 'img/kaleidoscope.jpg'; 

    kaleidoscope = new Kaleidoscope({ 
     image: image, 
     slices: 10 
    }); 

    kaleidoscope.domElement.style.position = 'absolute'; 

    kaleidoscope.domElement.style.marginLeft = -kaleidoscope.radius + 'px'; 

    kaleidoscope.domElement.style.marginTop = -kaleidoscope.radius + 'px'; 

    kaleidoscope.domElement.style.left = '50%'; 

    kaleidoscope.domElement.style.top = '50%'; 

    document.getElementsByTagName('header')[0].appendChild(kaleidoscope.domElement); 

    dragger = new DragDrop(function(data) { 
     return kaleidoscope.image.src = data; 
    }); 

    tx = kaleidoscope.offsetX; 

    ty = kaleidoscope.offsetY; 

    tr = kaleidoscope.offsetRotation; 

    onMouseMoved = (function(_this) { 
     return function(event) { 
      var cx, cy, dx, dy, hx, hy; 
      cx = window.innerWidth/10; 
      cy = window.innerHeight/10; 
      dx = event.pageX/window.innerWidth; 
      dy = event.pageY/window.innerHeight; 
      hx = dx - 0.5; 
      hy = dy - 0.5; 
      tx = hx * kaleidoscope.radius * -2; 
      ty = hy * kaleidoscope.radius * 2; 
      return tr = Math.atan2(hy, hx); 
     }; 
    })(this); 

    window.addEventListener('mousemove', onMouseMoved, false); 

    options = { 
     interactive: true, 
     ease: 0.1 
    }; 

    (update = (function(_this) { 
     return function() { 
      var delta, theta; 
      if (options.interactive) { 
       delta = tr - kaleidoscope.offsetRotation; 
       theta = Math.atan2(Math.sin(delta), Math.cos(delta)); 
       kaleidoscope.offsetX += (tx - kaleidoscope.offsetX) * options.ease; 
       kaleidoscope.offsetY += (ty - kaleidoscope.offsetY) * options.ease; 
       kaleidoscope.offsetRotation += (theta - kaleidoscope.offsetRotation) * options.ease; 
       kaleidoscope.draw(); 
      } 
      return setTimeout(update, 1000/60); 
     }; 
    })(this))(); 

    onChange = (function(_this) { 
     return function() { 
      kaleidoscope.domElement.style.marginLeft = -kaleidoscope.radius + 'px'; 
      kaleidoscope.domElement.style.marginTop = -kaleidoscope.radius + 'px'; 
      options.interactive = false; 
      return kaleidoscope.draw(); 
     }; 
    })(this); 
}); 

Z tego co widziałem, problem występuje tylko wtedy, gdy płótno jest na pełnym ekranie. Jeśli pojawi się na małej powierzchni, działa płynnie. Jednak na mojej stronie będzie to pełny ekran.

+0

Upewnij się, że w Safari jest włączone przyspieszone renderowanie sprzętu (GPU). – Blindman67

+0

@ Blindman67 Próbowałem włączyć go za pomocą menu debugowania, ale nadal nie poprawia szybkości odtwarzania. – Lucio

+0

Masz w tym coś naprawdę złego: ** nie zmieniaj rozmiaru elementu canvas w każdej klatce **, powoduje to ponowne odbicie strony i resetuje wszystkie właściwości twojego płótna; nie twórz wzorca w każdej klatce, jeśli zawsze jest taka sama, jaka będzie używana; unikaj przy maksymalnych 'save' i' restore', kiedy potrzebujesz zresetować transformacje (wolisz 'setTransform (1,0,0,1,0,0);'); nie używaj 'setTimeout' do tworzenia pętli animacji, preferuj' requestAnimationFrame'. Ale nawet przy [wszystko to naprawione] (https://jsfiddle.net/sd1skrj8/4/show/), Safari po prostu nie może sobie z tym poradzić ... Przepraszam. Próbowałem; nie udało się. – Kaiido

Odpowiedz

3

Odnośnie całej optymalizacji dokonanej w twoim kodzie oraz faktu, że na safari nadal ma framerate w pobliżu zera. Próbowałem zmodyfikować obraz, którego używasz, aby zmniejszyć rozmiar (jakość jpg 60, 30, 10), zmienić format obrazu (png24, png8), zmienić rozmiar obrazu (250x500 zamiast 750x1500) i wszystkie te zmiany nic nie zmieniły . Nadal dużo opóźnia się.

Potem próbowałem znaleźć niektóre testy porównawcze wykonane przy użyciu przeglądarki Safari Canvas. Znalazłem ten wykres, który pokazuje, że występy z Safari z płótnem nie są najlepsze.

Rend times, varying drawing area height

Można zobaczyć pełną benchmarku artykuł here

myślę, że w końcu, nawet po optymalizacji wykonane przez @Jorge Fuentes González, Twój kod jest nadal powolne renderowanie na Safari to może nie ma powód i to jest w rdzeniu silnika Webkita.

+0

Dzięki za wyjaśnienie, jest to najlepsza jak dotąd odpowiedź. Po prostu muszę pozostać przy słabej optymalizacji implementacji Webkita w Safari. – Lucio

+0

Tak, przykro mi złożyć złe wieści! Będziesz musiał umieścić duży mod, gdy użytkownik jest w Safari> _ < – dib258

8

Woah! Główny problem polega na tym, że rysujesz OGROMNE płótno. Tworzysz płótno WAY większe niż rozmiar okna. Chociaż część płótna nie jest pokazana, obliczenia do narysowania na tym obszarze są i tak wykonywane. Trzeba tylko narysować piksele, które można wyświetlić.

Tutaj można zobaczyć rzeczywisty rozmiar płótnie: http://i.imgur.com/trOYlcV.png

Z tego i porad @Kaiido Stworzyłem ten skrzypce: https://jsfiddle.net/Llorx/sd1skrj8/9/

Mój rozmiar płótna: http://i.imgur.com/4BzmCqh.png

ja po prostu stworzył płótna wypełnia całkowicie rzut i narysuj w środku, zwiększając promień łuku, będąc płótnem ograniczającym piksel "rzutni", a nie okno.

Zmieniono:

this.context.arc(0, 0, this.radius, step * -0.51, step * 0.51); 
// [...] 
kaleidoscope.domElement.style.marginLeft = -kaleidoscope.radius + 'px'; 
kaleidoscope.domElement.style.marginTop = -kaleidoscope.radius + 'px'; 
kaleidoscope.domElement.style.left = '50%'; 
kaleidoscope.domElement.style.top = '50%'; 

dla

this.context.arc(0, 0, this.radius*1.5, step * -0.51, step * 0.51); 
// [...] 
kaleidoscope.domElement.style.width = "100vw"; 
kaleidoscope.domElement.style.height = "100vh"; 
kaleidoscope.domElement.style.left = 0; 
kaleidoscope.domElement.style.top = 0; 

To może być poprawiona, aby mieć rzeczywisty krąg gdy stosunek ekran nie jest kwadratowy, a takich, ale masz pomysł: Nigdy nie płótno większy niż potrzebny.

PD: Nie testuj Safari. Powiedz mi, czy to poprawia wydajność.

+0

Dziękuję za usprawnienia, ale wciąż jest to bardzo powolne w Safari podczas oglądania w trybie pełnoekranowym ... – Lucio

+0

@ Luu Sprawdźmy, czy ktoś wie coś więcej. Sądzę, że najlepsze, co możesz teraz zrobić, to trzymać się CSS i transformacji 3D. Nie znam wydajności tego w Safari. –

0

Interesująca wada, usunęłabym stylizację css na płótnie, jak sugeruje @ Jorge, a następnie renderowałbym efekt na płótnie poza ekranem, a następnie kopiowałem renderowaną ramkę na widoczne płótno. Renderer DOM nie musi się wtedy martwić obcięciem poza ekranem.