<div id="root"></div>
html,
body {
  height: 100%;
}

#root {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background: #0F141A;
}

.app {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 12px;
}

.reload {
  padding: 8px
}

.image {
  display: block;
  width: 400px;
  height: 225px;
  animation: var(--animation-name) 2s;
  animation-fill-mode: forwards;
  mask-image: linear-gradient(
    rgb(0, 0, 0),
    rgb(0, 0, 0)
  );
  mask-position: center;
  mask-repeat: no-repeat;
  opacity: 0;
}

@keyframes reveal {
  0% {
    mask-size: 0 0;
  }
  100% {
    mask-size: 100% 100%;
  }
}
const imageUrl = "https://assets.codepen.io/721250/apple-pie1.jpg";

function FadeInImage({ src }) {
  const [loaded, setLoaded] = React.useState(false);
  return (
    <img
      className="image"
      src={src}
      onLoad={() => setLoaded(true)}
      style={
        loaded ? {
          "--animation-name": "reveal",
          opacity: 1
        } : {}} />
  )
}

function App() {
  const [key, setKey] = React.useState(0);
  return (
    <div class="app">
      <FadeInImage src={imageUrl} key={key} />
      <button class="reload" onClick={() => setKey(k => k + 1)}>再読み込み</button>
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.querySelector("#root")
);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js