Safari的requestAnimationFrame运行在30帧每秒。

3

我有一个基本的场景,可以移动和渲染平面。

  • 在Chrome中帧率为120 fps
  • 在Safari中帧率为30 fps

如何使这两者正常化,并获得类似的性能,最好接近120fps?

Safari的30fps让我很头疼。

到目前为止,我尝试使用:

this.renderer = new THREE.WebGLRenderer({
  canvas: this.canvas,
  powerPreference: "high-performance",
});

但是powerPreference属性似乎没有什么明显的区别,所以我认为需要修复requestAnimationFrame的定时。

const mod = (k, n) => ((k %= n) < 0) ? k+n : k;
const lerp = (v0, v1, t) => (1 - t) * v0 + t * v1;
const fpsElem = document.querySelector("#fps");

const ThreeCarousel = {
  clock: new THREE.Clock(),
  sizes: {
    width: window.innerWidth,
    height: window.innerHeight
  },
  slideGap: 2,
  slides: [],
  cols: [
    0xC4E7D4,
    0x998DA0,
    0xC4DACF,
    0xB9C0DA,
    0x63585E,
  ],
  p: 0,
  targetP: 0,
  currentX: 0,
  dragReduce: 0.01,
  wheelReduce: 0.001,
  s: 0.01,
  slidePosition(i){
    const max = this.slides.length * this.slideGap;
    const p = mod(this.p + (i * this.slideGap), max)
    
    return p - max / 2;
    
  },
  addObjects() {
    for(var i = 0; i < 5; i++){
      // const geometry = new THREE.BoxGeometry(1, 1, 1);
      const geometry = new THREE.PlaneGeometry(1.1, 1.5);
      const material = new THREE.MeshBasicMaterial({ color: this.cols[i] });
      const mesh = new THREE.Mesh(geometry, material);
      mesh.position.x = this.slidePosition(i);
      this.slides.push(mesh);
      this.scene.add(mesh);
    }
  },
  addCamera() {
    this.camera = new THREE.PerspectiveCamera(
      75,
      this.sizes.width / this.sizes.height,
      0.1,
      100
    );
    this.camera.position.z = 3;
    this.scene.add(this.camera);
  },
  addRenderer() {
    this.renderer = new THREE.WebGLRenderer({
      canvas: this.canvas,
      powerPreference: "high-performance",
    });
    this.renderer.setSize(this.sizes.width, this.sizes.height);
    this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  },
  addEvents(){
    window.addEventListener("resize", this.resize.bind(this));
    this.canvas.addEventListener("pointerdown", this.startDrag.bind(this));
    this.canvas.addEventListener("pointerup", this.stopDrag.bind(this));
    this.canvas.addEventListener("pointercancel", this.stopDrag.bind(this));
    this.canvas.addEventListener("pointerout", this.stopDrag.bind(this));
    this.canvas.addEventListener("pointermove", this.drag.bind(this));
    this.canvas.addEventListener("wheel", this.wheelDrag.bind(this));
  },
  startDrag(e){
    this.dragging = true;
    this.currentX = e.screenX;
  },
  stopDrag(e){
    this.dragging = false;
  },
  drag(e){
    if(!this.dragging){
      return;
    }
    this.targetP = this.targetP - (this.currentX - e.screenX) * this.dragReduce;
    this.currentX = e.screenX;
  }, 
  wheelDrag(e){
    if(Math.abs(e.deltaY) > Math.abs(e.deltaX)){
      return;
    }
    e.preventDefault();
    e.stopPropagation();
    this.targetP += e.deltaX * this.dragReduce * -1;
  },
  resize(){
    // Update sizes
    this.sizes.width = window.innerWidth;
    this.sizes.height = window.innerHeight;

    // Update camera
    this.camera.aspect = this.sizes.width / this.sizes.height;
    this.camera.updateProjectionMatrix();

    // Update renderer
    this.renderer.setSize(this.sizes.width, this.sizes.height);
    this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  },
  init() {
    this.canvas = document.querySelector("canvas#carousel");
    this.scene = new THREE.Scene();
    this.addObjects();
    this.addCamera();
    this.addRenderer();
    this.addEvents();
    this.then = 0;

    const tick = (now) => {
      now *= 0.001;                          // convert to seconds
      const deltaTime = now - this.then;          // compute time since last frame
      this.then = now;                            // remember time for next frame
      const fps = 1 / deltaTime;             // compute frames per second
      fpsElem.textContent = fps.toFixed(1);  // update fps display
      this.p = lerp(this.p, this.targetP, 0.06);
      const elapsedTime = this.clock.getElapsedTime();
      this.slides.forEach((slide, i) => {
        slide.position.x = this.slidePosition(i);
        slide.rotation.y = (Math.PI / 30) * slide.position.x + Math.PI / 12;
      })
      this.renderer.render(this.scene, this.camera);
      window.requestAnimationFrame(tick);
    };

    tick();
  }
};

ThreeCarousel.init();
html, body, canvas{
  height: 100%;
}

div{
  position: fixed;
  top: 0;
  left: 0;
  color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.145.0/three.min.js"></script>

<canvas id="carousel"></canvas>

<div>fps: <span id="fps"></span></div>

关于限制帧速率和减缓它们的旧答案,但在Safari上似乎没有任何有效方法可以以60fps运行。这里是此处找到的一个演示的屏幕截图:

enter image description here


你不能做太多事情来覆盖用户的电源偏好设置。事实上,应该尊重他们的选择。如果我的电池电量不足,我想要低功耗模式,我不希望某个网站去覆盖我的意愿并消耗它的剩余电量。许多人将显示器刷新率设置为60hz,因此强制使用120fps只会无谓地渲染看不到的帧。这就是 requestAnimationFrame 存在的原因;以尊重用户的设置。 - M -
@Marquizzo 我不是针对你说的,但是很奇怪上面的截图是在同一台机器上使用两个不同的浏览器拍摄的。但你是对的。我已经将我的逻辑分成了一个以60fps运行的tick函数和一个以指定的requestAnimationFrame间隔运行的渲染函数。 - Djave
1个回答

7

我感到尴尬——在“系统偏好设置”>“电池”>“电源适配器”中,我发现“低电量模式”被勾选了。取消勾选可以让我得到一个相当不令人印象深刻但还过得去的60fps。

不确定如何检测其他客户端是否在此模式下运行以相应地更新我的应用程序,但必须找到一种解决方法。

输入图像描述


抱歉,但这个问题和答案与编码有什么关系? - Rabbid76
4
这个问题涉及到JavaScript函数requestAnimationFrame。答案展示了如何使该函数以60fps运行。 - Djave
我理解你的观点。然而,在我看来,这不是一个适合在 Stack Overflow 上提出的问题。 - Rabbid76
4
我也理解你的观点,但是在找到(虽然不是代码方案)后,认为它可能会帮助遇到同样问题的人。也许可以给它一个踩,然后我们都可以继续自己的生活。 - Djave
完全没有关系。我们只是有不同意见,仅此而已。 - Rabbid76

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接