React JS: размер изображения изменяется при перезагрузке с помощью свойства object-fit: cover CSS

#javascript #html #css #reactjs #redux

#javascript #HTML #css #reactjs #сокращение

Вопрос:

Я показываю изображение в приложении react, созданном с использованием CRA. Каждый раз, когда я перезагружаю веб-страницу, изображение странно мерцает, как показано ниже.

Изображение изначально:

введите описание изображения здесь

Изображение после мерцания (фактическое требование):

введите описание изображения здесь

Код:

styles.css:

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

app.tsx

 import React from 'react';
import foo from './foo.jpg';

import './styles.css';

const App = () => <img src={foo} alt="foo" />;

export default App;
  

package.json

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

Примечания:

  1. Приложение представляет собой внедренный шаблон без каких-либо установленных сторонних библиотек или каких-либо других компонентов, кроме этого изображения.
  2. Проблема, описанная выше, устраняется, если object-fit: cover; свойство удалено из таблицы стилей, но это необходимо для предотвращения странного растягивания / сжатия изображения, как на исходном изображении.
  3. Если проблема не воспроизводима, пожалуйста, оставьте консоль разработчика открытой или попробуйте изменить сеть на любую из настроек 3G. Я могу легко воспроизводить повторяющиеся перезагрузки.
  4. Я считаю, что object-fit: cover это свойство изначально не применяется к изображению, и для его запуска требуется несколько миллисекунд.
  5. Обратите внимание, что структура DOM не отображается в консоли разработчика в исходном изображении.
  6. Любая альтернатива свойству CSS также была бы полезна.

Комментарии:

1. Попробуйте создать codesandbox

2. @DennisVash это невозможно воспроизвести в codesandbox.

3. Почему бы и нет? Он также должен мерцать в песочнице

4. @DennisVash Я считаю, что codesandbox не отображает приложение React в реальном браузере, и мы видим, что React просто отображается в div на их платформе. Итак, если я перезагружу codesandbox, он не будет работать так, как это делал бы реальный браузер в системе. То же самое будет, если я попытаюсь перезагрузить, нажав на значок перезагрузки, указанный там.

5. Это не так, как это работает… Вы на самом деле сказали, что запускаете браузер в браузере, codesandbox просто отображает код, который вы пишете, вы можете проверить его и убедиться сами, более того, у него есть CRA starter, поэтому он точно имитирует любую машину

Ответ №1:

Повозившись с этим довольно долго, я в значительной степени убежден, что это ошибка производительности в браузерах Chromium. В частности, это, по-видимому, связано с кэшированием изображений и оптимизациями, которые браузер выполняет при загрузке изображений в img контейнер с явными размерами высоты и ширины.

Я говорю это, потому что в моих тестах очистка кэша перед перезагрузкой заставит браузер применить object-fit до того, как произойдет первая покраска. Я также заметил, что независимо от object-fit наличия, кэшированные изображения будут загружены в img контейнер с определенной высотой и шириной за долю секунды до img контейнеров только с одним или ни с одним из этих размеров. Похоже, именно здесь возникает ошибка. object-fit Правило применяется только во время второго слоя рисования.

Может быть, вы можете попытаться воспроизвести это со следующим фрагментом для подтверждения? Я использовал здесь случайное изображение-заполнитель, но вы можете захотеть использовать локальное изображение. Если кто-нибудь может предоставить более подробное объяснение, мне было бы интересно его услышать. Как упоминалось в комментариях, это воспроизводимо только с помощью HTML / CSS и сетевого регулирования, хотя я обнаружил, что эффект гораздо более выражен в контексте React:

 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);  
 .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%);
}  
 <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>  

Несмотря на это, второе изображение в приведенном выше фрагменте является одним из возможных решений этой проблемы, если она продолжает доставлять вам огорчения. Вы выбираете размер, который хотите масштабировать в родительском контейнере, задавая img либо 100% высоту, либо ширину. Затем его можно центрировать (или иным образом отрегулировать) с помощью комбинации смещения направления и преобразования. Очевидно, что это не так динамично, как object-fit потому, что вам нужно заранее решить, как масштабировать изображение, чтобы получить cover contain эффект или.

Некоторые альтернативные решения включают:

1. Вместо этого используйте фоновое изображение

Я обнаружил, что свойство background CSS, которое object-fit в конечном итоге было получено из, не страдает от той же ошибки. Используйте a div вместо img тега и поместите изображение на его фон:

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

2. Предварительная загрузка (HTML)

Другим исправлением, которое я нашел, была предварительная загрузка изображения, которая сигнализирует браузеру, что это критический ресурс и его следует сразу извлечь. Это, по-видимому, подтверждает идею о том, что проблема в конечном итоге сводится к оптимизации браузера.

К сожалению, это на самом деле не соответствует стандартам React, поскольку у вас есть только одна HTML-страница, и ее можно легко заполнить этими ссылками, но она работает независимо. Поместите это в head :

 <link rel="preload" href="path/yourimage.jpg" as="image"/>
  

3. Предварительная загрузка (реакция)

Вы можете имитировать предварительную загрузку в 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;
};
  

Или, если вы загружаете пакет изображений, вы можете подождать, пока они все будут загружены, прежде чем отображать сам компонент / пакет. Это связано с некоторыми недостатками, связанными с потенциальным сдвигом макета, и может потребовать некоторой оптимизации для обработки больших пакетов, но вы поняли идею:

 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 amp;amp; (
        <>
            <img src={foo} alt="foo"></img>
            <img src={bar} alt="bar"></img>
        </>
    );
};
  

Комментарии:

1. Я действительно ценю, что вы так глубоко вникли в эту проблему. Я внимательно прочитаю ваш ответ. Но я прочитал кратко, и я хотел бы добавить, что изображения не загружаются в приложение, а хранятся на сервере. Итак, я не думаю, что использование <link> было бы хорошей практикой, и, кроме того, мне придется добавлять inline style ко всем компонентам в моем приложении, чтобы предоставить фоновое изображение для каждого из них, что снизило бы производительность. И, в любом случае, нужно ли поднимать проблему для браузеров chromium или производственной сборки Chrome, чтобы быть конкретным?

2. Вы абсолютная легенда для этой статьи. Я не могу поверить, насколько одинок этот пост. Это золото, большое вам спасибо!

3. Использование фонового изображения работает! Спасибо! Мой вариант использования: <div style={{ background: `url('${imageUrl}') center / contain`, backgroundRepeat: 'no-repeat' }}></div>