Kureev / react-native-blur

React Native Blur component
MIT License
3.75k stars 555 forks source link

BlurView does not update when scrolling ScrollView #189

Open thurt opened 7 years ago

thurt commented 7 years ago

Hi,

I have only tried setting this up on android for starters and I have hit a road block. The initial blurring works fine so I'm confident I have setup the viewRef correctly.

This shows the issue I am experiencing: http://recordit.co/9k6dztRzBo

The BlurView does not update as I scroll the underlying ScrollView. The component order looks something like:

<View>
   <ScrollView> // this ref is used as the viewRef
      <Component />
   </ScrollView>
   <BlurView /> // this receives the viewRef
</View>

My best guess is that the BlurView is not updating b/c the positioning of the overall ScrollView box doesn't change (just the contents inside are scrolling). Any other ideas?

Thanks!

julesmoretti commented 7 years ago

Tried it also same issue...

Here is the code I used based of the example:

/**
 * Basic [Android] Example for react-native-blur
 * https://github.com/react-native-community/react-native-blur
 */
'use strict';
import React, { Component } from 'react';
import {
  AppRegistry,
  Image,
  findNodeHandle,
  StyleSheet,
  Text,
  View,
  ScrollView,
  Dimensions,
  Switch,
  InteractionManager,
} from 'react-native';
import AndroidSegmented from 'react-native-segmented-android';

import { BlurView } from 'react-native-blur';

const BLUR_TYPES = ['xlight', 'light', 'dark'];

class Basic extends Component {
  constructor() {
    super();
    this.state = {
      showBlur: true,
      viewRef: null,
      activeSegment: 2,
      blurType: 'dark',
    };
  }

  componentDidMount() {
    this.imageLoaded();
  }

  imageLoaded() {
    // Workaround for a tricky race condition on initial load.
    InteractionManager.runAfterInteractions(() => {
      setTimeout(() => {
        this.setState({ viewRef: findNodeHandle(this.refs.backgroundImage) });
      }, 500);
    });
  }

  _onChange(selected) {
    this.setState({
      activeSegment: selected,
      blurType: BLUR_TYPES[selected],
    });
  }

  renderBlurView() {
    const tintColor = ['#ffffff', '#000000'];
    if (this.state.blurType === 'xlight') tintColor.reverse();

    return (
      <View
        // style={[styles.container, {
        style={[{
          position: 'absolute',
          top: 0,
          left: 100,
          right: 0,
          height: 400,
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: 'transparent',
        }]}
      >
        {this.state.viewRef && <BlurView
          viewRef={this.state.viewRef}
          style={styles.blurView}

          blurRadius={9}
          blurType={this.state.blurType}

          // The following props are also available on Android:

          // blurRadius={20}
          // downsampleFactor={10}
          // overlayColor={'rgba(0, 0, 255, .6)'}   // set a blue overlay
        />}

        <Text style={[styles.text, { color: tintColor[0] }]}>
          Blur component (Android)
        </Text>

        <AndroidSegmented
          tintColor={tintColor}
          style={{
            width: Dimensions.get('window').width,
            height: 28,
            justifyContent: 'center',
            alignItems: 'center',
          }}
          childText={BLUR_TYPES}
          orientation='horizontal'
          selectedPosition={this.state.activeSegment}
          onChange={this._onChange.bind(this)} />
      </View>
    )
  }

  render() {
    return (
      <View
        style={styles.container}
      >
        <ScrollView
          style={{
            flex: 1,
            backgroundColor: 'red',
          }}
          ref={'backgroundImage'}
          // onScroll={this.imageLoaded.bind(this)}
          onScroll={() => console.log("scrolling")}
        >
          <Image
            source={require('./bgimage.jpeg')}
            style={styles.image}
          />
          <Image
            source={require('./bgimage.jpeg')}
            style={styles.image}
            // ref={'backgroundImage'}
            // onLoadEnd={this.imageLoaded.bind(this)}
          />
        </ScrollView>

        { this.state.showBlur ? this.renderBlurView() : null }

        <View
          style={styles.blurToggle}>
          <Switch
            onValueChange={(value) => this.setState({showBlur: value})}
            value={this.state.showBlur} />
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: 'transparent',
  },
  image: {
    // position: 'absolute',
    // left: 0,
    // top: 0,
    // bottom: 0,
    // right: 0,
    resizeMode: 'cover',
    width: 200,
    height: 400,
  },
  blurView: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
  },
  text: {
    fontSize: 22,
    fontWeight: 'bold',
    textAlign: 'center',
    margin: 10,
    color: '#FFFFFF',
  },
  blurToggle: {
    position: 'absolute',
    top: 30,
    right: 10,
    alignItems: 'flex-end',
  },
});

AppRegistry.registerComponent('Basic', () => Basic);
julesmoretti commented 7 years ago

It seems as though Blur View takes a snapshot of the view you want to blur and uses that as the background.

So any offsets will not work on Android.

So 2 things:

Is there a way to adjust the representation of the Blur View by the ability to crop the BlurView's reference?

And is there a way to refreshing the BlurView onScroll.

Ow and one thing I noticed, the BlurView stops working when Debug JS Remotely is on.

julesmoretti commented 7 years ago

@Kureev - any advice?

Kureev commented 7 years ago

@julesmoretti unfortunately, nothing specific. Blur takes a snapshot of underlying view and blurs it. If you refresh onScroll, it will literally kill your performance 😞

julesmoretti commented 7 years ago

@Kureev - thank you very much :(

to refresh I would have to call the imageLoaded() again right?

And last question for you is there any limitations, for example would it still work on Animated.View's?

Kureev commented 7 years ago

Depends what you want to do. One important thing you need to keep in mind is that once the screen is drawn, it'll remain "unchanged" till the next re-draw. Simply put, it isn't aware of any changes in the child / underlying components.

I know that it isn't quite useful, but that's a known issue. Taking snapshot of underlying views and calculating blur every frame may significantly drop performance of the app :(

DomiR commented 7 years ago

Someone opened an PR some time ago, but you would still need to invalidate the view on scroll events. Something, that is not supported yet, but could be implemented in react-native-blur. https://github.com/500px/500px-android-blur/pull/10/files

f-ricci commented 3 years ago

for whoever gets here looking for solutions, I fixed this by changing the order elements in the hierarchy tree.

This DOESN'T work

<BlurView
        blurType="dark"
        blurAmount={12}
        blurRadius={24}
        style={{
          position: 'absolute',
          zIndex: 999,
          left: 0,
          right: 0,
          top: 0,
          height: 500,
        }}
      >
 {children}
</BlurView>
<ScrollView>
  {children}
</ScrollView>

This works

<ScrollView>
  {children}
</ScrollView>
<BlurView
        blurType="dark"
        blurAmount={12}
        blurRadius={24}
        style={{
          position: 'absolute',
          zIndex: 999,
          left: 0,
          right: 0,
          top: 0,
          height: 500,
        }}
      >
 {children}
</BlurView>
nickplee commented 3 years ago

Above fixed it for me. Thanks @f-ricci!