Skip to content

Instantly share code, notes, and snippets.

@shafayeatsumit
Created May 13, 2020 20:22
Show Gist options
  • Save shafayeatsumit/e1a426780d0472397eac8daacb2ff0c9 to your computer and use it in GitHub Desktop.
Save shafayeatsumit/e1a426780d0472397eac8daacb2ff0c9 to your computer and use it in GitHub Desktop.
expand and shrink svg circle in react native
<ImageBackground source={DEFAULT_IMAGE} style={{flex: 1}}>
<Svg width={ScreenWidth} height={ScreenHeight}>
<Mask id="myMask">
<Rect x="0" y="0" width="100%" height="100%" fill="white" />
<Circle
cx={ScreenWidth / 2}
cy={ScreenHeight / 2}
r={100}
fill="black"
/>
</Mask>
<Rect
x="0"
y="0"
width="100%"
height="100%"
mask="url(#myMask)"
fill="rgba(0,0,0,0.8)"
/>
</Svg>
</ImageBackground>
@shafayeatsumit
Copy link
Author

import {
  View,
  Text,
  Animated,
  StyleSheet,
  TouchableOpacity,
  Button,
  Image,
  ImageBackground,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
// import styles from './Profile.styles.js';
import analytics from '@react-native-firebase/analytics';
import {useFocusEffect} from '@react-navigation/native';
import DEFAULT_IMAGE from '../../../assets/background_imges/image_6.png';
import {ScreenWidth, ScreenHeight} from '../../helpers/constants/common.js';
import {
  Svg,
  Defs,
  Rect,
  Mask,
  Circle,
  ClipPath,
  Path,
  Polygon,
} from 'react-native-svg';

const SvgCircle = (props) => {
  const [radius, setRadius] = useState(10);

  return (
    <Svg height="100%" width="100%" onPress={() => setRadius(radius + 10)}>
      <Defs>
        <Mask id="mask" x="0" y="0" height="100%" width="100%">
          <Rect height="100%" width="100%" fill="#fff" />
          <Circle
            r={`${60}%`}
            cx="50%"
            cy="50%"
            stroke="red"
            strokeWidth="1"
            fill="black"
          />
        </Mask>
      </Defs>
      <Rect
        height="100%"
        width="100%"
        fill="rgba(0, 0, 0, 0.8)"
        mask="url(#mask)"
        fill-opacity="0"
      />
    </Svg>
  );
};

const Profile = () => {
  return (
    <View style={styles.container}>
      <ImageBackground source={DEFAULT_IMAGE} style={styles.image}>
        <SvgCircle />
      </ImageBackground>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
  },
  image: {
    flex: 1,
    resizeMode: 'cover',
    justifyContent: 'center',
  },
  text: {
    color: 'grey',
    fontSize: 30,
    fontWeight: 'bold',
  },
});
export default Profile;

@shafayeatsumit
Copy link
Author

import React, {useState} from 'react';
import {
  View,
  Text,
  Animated,
  StyleSheet,
  TouchableOpacity,
  Button,
  Image,
  Easing,
  ImageBackground,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
// import styles from './Profile.styles.js';
import analytics from '@react-native-firebase/analytics';
import {useFocusEffect} from '@react-navigation/native';
import DEFAULT_IMAGE from './assets/background_imges/image_4.png';

import {
  Svg,
  Defs,
  Rect,
  Mask,
  Circle,
  ClipPath,
  Path,
  Polygon,
} from 'react-native-svg';
const AnimatedCircle = Animated.createAnimatedComponent(Circle);

const SvgCircle = (props) => {
  let circleState = false;
  const radius = new Animated.Value(1);
  const expandCircle = () => {
    // Animated.timing(radius).stop();
    Animated.timing(radius, {
      toValue: 7,
      duration: 10000,
      useNativeDriver: true,
      easing: Easing.linear,
    }).start();
    circleState = true;
  };
  const shrinkCircle = () => {
    circleState = false;
    // Animated.timing(radius).stop();
    Animated.timing(radius, {
      toValue: 1,
      duration: 10000,
      useNativeDriver: true,
      easing: Easing.linear,
    }).start();
  };

  const handleCircle = () => {
    if (!circleState) {
      console.log('expand Circle');
      expandCircle();
    } else {
      console.log('shrink Circle');
      shrinkCircle();
    }
  };
  const radiusPercent = radius.interpolate({
    inputRange: [1, 7],
    outputRange: ['10%', '70%'],
  });

  return (
    <Svg height="100%" width="100%" onPress={handleCircle}>
      <Defs>
        <Mask id="mask" x="0" y="0" height="100%" width="100%">
          <Rect height="100%" width="100%" fill="#fff" />
          <AnimatedCircle
            r={radiusPercent}
            cx="50%"
            cy="50%"
            stroke="red"
            strokeWidth="1"
            fill="black"
          />
        </Mask>
      </Defs>
      <Rect
        height="100%"
        width="100%"
        fill="rgba(0, 0, 0, 0.8)"
        mask="url(#mask)"
        fill-opacity="0"
      />
    </Svg>
  );
};

const Profile = () => {
  return (
    <View style={styles.container}>
      <ImageBackground source={DEFAULT_IMAGE} style={styles.image}>
        <SvgCircle />
      </ImageBackground>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
  },
  image: {
    flex: 1,
    resizeMode: 'cover',
    justifyContent: 'center',
  },
  text: {
    color: 'grey',
    fontSize: 30,
    fontWeight: 'bold',
  },
});
export default Profile;

@shafayeatsumit
Copy link
Author

shafayeatsumit commented May 15, 2020

// full implementation

import React, {useState, useEffect} from 'react';
import {
  View,
  Animated,
  StyleSheet,
  Easing,
  ImageBackground,
} from 'react-native';
import {TapGestureHandler, State} from 'react-native-gesture-handler';
import DEFAULT_IMAGE from './assets/background_imges/image_4.png';

import {Svg, Defs, Rect, Mask, Circle} from 'react-native-svg';

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

const SvgCircle = (props) => {
  const radius = new Animated.Value(1);
  const [fullSceeen, setFullScreen] = useState(false);
  const expandCircle = () => {
    Animated.timing(radius, {
      toValue: 7,
      duration: 10000,
      useNativeDriver: true,
      easing: Easing.ease,
    }).start();
  };
  const shrinkCircle = () => {
    Animated.timing(radius, {
      toValue: 1,
      duration: 10000,
      useNativeDriver: true,
      easing: Easing.linear,
    }).start();
  };

  const onStateChange = ({nativeEvent}) => {
    if (fullSceeen) {
      return;
    }
    if (nativeEvent.state === State.BEGAN) {
      expandCircle();
    } else if (nativeEvent.state === State.END) {
      shrinkCircle();
    }
  };
  useEffect(() => {
    const animationId = radius.addListener(({value}) => {
      value === 7 && setFullScreen(true);
    });
    return () => radius.removeListener(animationId);
  }, []);

  const radiusPercent = radius.interpolate({
    inputRange: [1, 7],
    outputRange: ['10%', '70%'],
  });

  return (
    <TapGestureHandler onHandlerStateChange={onStateChange}>
      <Svg height="100%" width="100%">
        <Defs>
          <Mask id="mask" x="0" y="0" height="100%" width="100%">
            <Rect height="100%" width="100%" fill="#fff" />
            <AnimatedCircle
              r={fullSceeen ? '100%' : radiusPercent}
              cx="50%"
              cy="50%"
              stroke="red"
              strokeWidth="1"
              fill="black"
            />
          </Mask>
        </Defs>
        <Rect
          height="100%"
          width="100%"
          fill="rgba(0, 0, 0, 0.8)"
          mask="url(#mask)"
          fill-opacity="0"
        />
      </Svg>
    </TapGestureHandler>
  );
};

const Profile = () => {
  return (
    <View style={styles.container}>
      <ImageBackground source={DEFAULT_IMAGE} style={styles.image}>
        <SvgCircle />
      </ImageBackground>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
  },
  image: {
    flex: 1,
    resizeMode: 'cover',
    justifyContent: 'center',
  },
  text: {
    color: 'grey',
    fontSize: 30,
    fontWeight: 'bold',
  },
});
export default Profile;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment