hguelyon
hguelyon

Reputation: 63

Reactjs Howto Blur Background image on scroll?

I'm new to reactjs and I would like understand how to reproduce this jquery example in Reactjs styled-components : Blurring background image on event "Scroll" https://jsfiddle.net/BinaryMoon/8jhw2/

$(document).ready(function() {
    $(window).scroll(function(e) {
        var s = $(window).scrollTop(),
            opacityVal = (s / 200);

        $('.blurred-image').css('opacity', opacityVal);
    });
});
.img-src { 
    position: absolute;
    background:url(http://bromleydemo.files.wordpress.com/2013/10/blossom.jpg?w=600) center center;
    background-size: cover;
}

as you've seen the background url is set into css prop I've tried something in reactjs but each time I scroll, the image background is reloaded. Can somebody help me ?

ps : If the solution can be implemented with styled-components it will be awesome

ps2 : this is another page of the effect i'm trying to implement https://codepen.io/zrichard/pen/wEFBd

Thank you

Upvotes: 3

Views: 7017

Answers (2)

hguelyon
hguelyon

Reputation: 63

Your sample code is really good except one thing which is very difficult to implement properly with all these frameworks : SCROLLING.

If you open a debug Console, you'll see this kind of warning message (when you scroll up and down, the display will be "blank" if you scroll quickly)

Over 200 classes were generated for component bg-opacity-v2__ImgSrc. Consider using the attrs method, together with a style object for frequently changed styles.

Example:
  const Component = styled.div.attrs({
    style: ({ background }) => ({
      background,
    }),
  })`width: 100%;`

  <Component />

I find a solution using component ".attrs" method.

const ImgSrc = styled.div.attrs(props => ({
  style: {
    opacity: `${props.opacity}`,
    filter: "blur(" + `${props.filter}` + "px) brightness(0.7)",
  },
}))`
  position: absolute;
  background: url("http://bromleydemo.files.wordpress.com/2013/10/blossom.jpg?w=600")
    center center;
  background-size: cover;
  top: -10%;
  bottom: 10%;
  left: -10%;
  right: 10%;
  width: 120%;
  height: 120%;
`

Here is a new version of your code. I deleted the first tag , modify the opacity computation but it's a detail. I don't really understand why I have to extract the opacity computation from the "useEffect" method, but it works fine like that.

const onScroll = () => {
    setOpacity(scrollOpacity())
    setFilter(scrollFilter())
}

This new version works well.

Live Sandbox

import React, { useState, useEffect } from "react";
import styled, { css } from "styled-components";
import { hydrate, render } from "react-dom";

function scrollOpacity() {
  const winScroll =
    document.body.scrollTop || document.documentElement.scrollTop;

  const height =
    document.documentElement.scrollHeight -
    document.documentElement.clientHeight;

  const scrolled = winScroll / height;
  const newOpacity = 1 + scrolled * 2;
  return newOpacity;
}

function scrollFilter() {
  const winScroll =
    document.body.scrollTop || document.documentElement.scrollTop;

  const newFilter = 0 + winScroll / 100;
  return newFilter;
}

function App() {
  const [opacity, setOpacity] = useState(1);
  const [filter, setFilter] = useState(0);

  useEffect(() => {
    const onScroll = () => {
      setOpacity(scrollOpacity());
      setFilter(scrollFilter());
    };

    window.addEventListener("scroll", onScroll);

    return function cleanup() {
      window.removeEventListener("scroll", onScroll);
    };
  }, []);

  return (
    <React.Fragment>
      <BlurredImageContainer>
        <ImgSrc opacity={opacity} filter={filter} />
      </BlurredImageContainer>
      <Content>
        <Avatar src="https://pbs.twimg.com/profile_images/378800000748837969/bd8e553e5cae83ef488c6b15166bdd55.png" />
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
      </Content>
    </React.Fragment>
  );
}

const BlurredImageContainer = styled.div`
  display: block;
  padding: 0;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: -1;
`;

const ImgSrc = styled.div.attrs(props => ({
  style: {
    opacity: `${props.opacity}`,
    filter: "blur(" + `${props.filter}` + "px) brightness(0.7)"
  }
}))`
  position: absolute;
  background: url("http://bromleydemo.files.wordpress.com/2013/10/blossom.jpg?w=600")
    center center;
  background-size: cover;
  top: -10%;
  bottom: 10%;
  left: -10%;
  right: 10%;
  width: 120%;
  height: 120%;
`;

const Content = styled.div`
  padding: 150px 50px;
  color: #fff;
  text-align: center;
`;

const Avatar = styled.div`
  height: 120px;
  width: 120px;
  border-radius: 100%;
  border: 5px solid #fff;
  margin: 0 auto 50px;
`;

const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
  hydrate(<App />, rootElement);
} else {
  render(<App />, rootElement);
}

If anybody have an optimization or better way to do this, your are welcomed.

Upvotes: 3

Cody Bennett
Cody Bennett

Reputation: 786

You can bind an event listener to the window using the useEffect hook.

Live sandbox.

import React, { useState, useEffect } from "react";
import styled, { css } from "styled-components";
import { hydrate, render } from "react-dom";

function App() {
  const [opacity, setOpacity] = useState(0);

  useEffect(() => {
    const onScroll = () => setOpacity(window.scrollY / 200);

    window.addEventListener("scroll", onScroll);

    return function cleanup() {
      window.removeEventListener("scroll", onScroll);
    };
  }, []);

  return (
    <React.Fragment>
      <BlurredImageContainer>
        <ImgSrc />
        <ImgSrc blurred opacity={opacity} />
      </BlurredImageContainer>
      <Content>
        <Avatar src="https://pbs.twimg.com/profile_images/378800000748837969/bd8e553e5cae83ef488c6b15166bdd55.png" />
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
        <p>This is some test content</p>
      </Content>
    </React.Fragment>
  );
}

const BlurredImageContainer = styled.div`
  display: block;
  padding: 0;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: -1;
`;

const ImgSrc = styled.div`
  position: absolute;
  background: url("http://bromleydemo.files.wordpress.com/2013/10/blossom.jpg?w=600") center center;
  background-size: cover;
  top: -10%;
  bottom: 10%;
  left: -10%;
  right: 10%;
  width: 120%;
  height: 120%;

  ${props => props.blurred && css`
    opacity: ${props.opacity};
    filter: blur(20px) brightness(0.7);
  `};
`;

const Content = styled.div`
  padding: 150px 50px;
  color: #fff;
  text-align: center;
`;

const Avatar = styled.div`
  height: 120px;
  width: 120px;
  border-radius: 100%;
  border: 5px solid #fff;
  margin: 0 auto 50px;
`;

const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
  hydrate(<App />, rootElement);
} else {
  render(<App />, rootElement);
}

Upvotes: 0

Related Questions