React JS:使用 object-fit 重新加载时图像大小闪烁:覆盖 CSS 属性

Vin*_*rma 5 html javascript css reactjs redux

我正在使用 CRA 创建的 React 应用程序中显示图像。每次我重新加载网页时,图像都会奇怪地闪烁,如下所示。

\n

最初的图像:

\n

在此输入图像描述

\n

闪烁后的图像(实际要求):

\n

在此输入图像描述

\n

代码:

\n

样式.css:

\n
img {\n  width: 200px;\n  height: 300px;\n  object-fit: cover;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

应用程序.tsx

\n
import React from \'react\';\nimport foo from \'./foo.jpg\';\n\nimport \'./styles.css\';\n\nconst App = () => <img src={foo} alt="foo" />;\n\nexport default App;\n
Run Code Online (Sandbox Code Playgroud)\n

包.json

\n
{\n  "name": "xyz",\n  "version": "0.1.0",\n  "private": true,\n  "dependencies": {\n    "@testing-library/jest-dom": "^4.2.4",\n    "@testing-library/react": "^9.5.0",\n    "@testing-library/user-event": "^7.2.1",\n    "react": "^16.13.1",\n    "react-dom": "^16.13.1",\n    "react-scripts": "3.4.3"\n  },\n  "scripts": {\n    "start": "react-scripts start",\n    "build": "react-scripts build",\n    "test": "react-scripts test",\n    "eject": "react-scripts eject"\n  },\n  "eslintConfig": {\n    "extends": "react-app"\n  },\n  "browserslist": {\n    "production": [\n      ">0.2%",\n      "not dead",\n      "not op_mini all"\n    ],\n    "development": [\n      "last 1 chrome version",\n      "last 1 firefox version",\n      "last 1 safari version"\n    ]\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

笔记:

\n
    \n
  1. 该应用程序是一个注入的样板,没有安装任何第三方库,也没有安装除此图像之外的任何其他组件。
  2. \n
  3. 如果从样式表中删除属性,上述问题就会得到解决object-fit: cover;,但这是为了防止图像像初始图像一样奇怪地拉伸/收缩所必需的。
  4. \n
  5. 如果问题无法重现,请保持开发者控制台打开或尝试将网络更改为任何3G 预设。我可以轻松地通过重复重新加载来重现。
  6. \n
  7. 我相信该object-fit: cover属性最初不会\xe2\x80\x99 应用于图像,并且需要几毫秒才能启动。
  8. \n
  9. 请注意,DOM 结构\xe2\x80\x99 不会显示在初始图像的开发人员控制台中。
  10. \n
  11. CSS 属性的任何替代方案也会有所帮助。
  12. \n
\n

law*_*itt 11

Having messed around with this for a good while now, I am pretty much convinced that it is a performance bug with Chromium browsers. Specifically, it seems related to the caching of images, and the optimizations that the browser makes when loading images into an img container with explicit height and width dimensions.

I say this because in my tests, emptying the cache before a reload will make the browser apply object-fit before the first paint happens. I also noticed that regardless of object-fit being present, cached images will be loaded into an img container with defined height and width a split second before img containers with only one or neither of these dimensions. This appears to be where the bug is occuring. The object-fit rule is only being applied during the second paint layer.

Maybe you can try and reproduce this with the following snippet to confirm? I've used a random placeholder image here but you might want to use a local image. If anyone can provide a more detailed explanation I'd be interested to hear it. As mentioned in the comments, this is reproducible with only HTML/CSS and network throttling, although I have found the effect to be much more pronounced in a React context:

const { Fragment } = React;

const App = () => {
  return (
    <Fragment>
    
      <p>Object fit with two explicit dimensions</p>
      <img src="https://source.unsplash.com/RN6ts8IZ4_0/600x400" className="img-fit"></img>
      
      <p>Positioned element with one explicit dimension</p>
      <div className="container">
        <img src="https://source.unsplash.com/RN6ts8IZ4_0/600x400" className="img-position"></img>
      </div>
      
    </Fragment>
  );
};

ReactDOM.render(<App/>, document.body);
Run Code Online (Sandbox Code Playgroud)
.img-fit {
  width: 200px;
  height: 300px;
  object-fit: cover;
}

.container {
  width: 200px;
  height: 300px;
  overflow: hidden;
  position: relative;
}

.img-position {
  height: 100%;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

Regardless, the second image in the snippet above is one possible workaround for this issue if it continues to give you grief. You choose the dimension you want to scale to on the parent container by making the img either 100% height or width. It can then be centred (or otherwise adjusted) with a combination of directional offset and transform. This is obviously not as dynamic as object-fit because you have to decide beforehand how to scale the image to get either a cover or contain effect.

Some alternative solutions include:

1. Use a background image instead

我发现最终派生的backgroundCSS 属性object-fit不会遇到同样的错误。使用 adiv代替img标签并将图像放在其背景上:

div {
  width: 200px;
  height: 300px;
  background: url('path/yourimg.jpg') center / cover;
}
Run Code Online (Sandbox Code Playgroud)

2. 预加载(HTML)

我发现的另一个修复是预加载图像,这向浏览器发出信号,表明它是关键资源,应该立即获取。这似乎支持了这样的观点:问题最终归结于浏览器优化。

不幸的是,这并不真正符合 React 标准,因为你只有一个 HTML 页面,并且它很容易被这些链接填满,但无论如何它都能工作。把这个放在head

<link rel="preload" href="path/yourimage.jpg" as="image"/>
Run Code Online (Sandbox Code Playgroud)

3.预加载(React)

您可以在 React 中模拟预加载,方法是等待图像的onload事件触发,然后再使其对用户可见。这可能与在 JSX 中使用相关类的单行代码一样快速且肮脏:

// app.js

const App = () => <img src={foo} onLoad={e => e.target.classList.add('visible')}/>;

// index.css

img {
  width: 200px;
  height: 300px;
  object-fit: cover;
  visibility: hidden;
}

.visible {
  visibility: visible;
};
Run Code Online (Sandbox Code Playgroud)

或者,如果您正在加载一批图像,您可以等待它们全部加载完毕,然后再显示组件/批次本身。这带来了一些潜在的布局变化的缺点,并且可能需要一些优化来处理大批量,但你明白了:

import foo from 'path/foo.jpg';
import bar from 'path/bar.jpg';

const App = () => {
    const [loaded, setLoaded] = useState(false);

    useEffect(() => {
        const loadArray = [foo, bar].map(src => {
            return new Promise((res, rej) => {
                const img = new Image();

                img.src = src;
                img.onload = () => res();
                img.onerror = () => rej();
            })
        });

        Promise.allSettled(loadArray).then(() => setLoaded(true));
    }, []);

    return loaded && (
        <>
            <img src={foo} alt="foo"></img>
            <img src={bar} alt="bar"></img>
        </>
    );
};
Run Code Online (Sandbox Code Playgroud)