user2675468
user2675468

Reputation: 145

Overlay is smaller than parent styled components in React Native

i have following problem with the styled components. I want to hide the background with a new circle, but the one in the front is smaller, even if I have the same values for size. Here is my code example:

import React from "react";
import { StyleSheet, View } from "react-native";

const SIZE = 50;

export default function App() {
  return (
    <View style={styles.container}>
      <View style={styles.layer1} />
      <View style={styles.layer2} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    alignItems: "center",
    justifyContent: "center",
  },
  layer1: {
    width: SIZE,
    height: SIZE,
    borderWidth: 3,
    borderRadius: SIZE / 2,
    borderColor: "black",
    position: "absolute",
  },
  layer2: {
    width: SIZE,
    height: SIZE,
    borderWidth: 3,
    borderRadius: SIZE / 2,
    borderColor: "white",
    position: "absolute",
  },
});

And here the screenshot:

Does someone know why the overlay is smaller than the background layer?

best regards and thanks!!!

Upvotes: 3

Views: 1326

Answers (3)

Neelam Soni
Neelam Soni

Reputation: 1361

See this code,, you can do it without using any external library : https://medium.com/@0saurabhgour/react-native-percentage-based-progress-circle-no-external-library-e25b43e83888

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {StyleSheet, View, ViewPropTypes, I18nManager} from 'react-native';

// compatability for react-native versions < 0.44
const ViewPropTypesStyle = ViewPropTypes
  ? ViewPropTypes.style
  : View.propTypes.style;
let direction = I18nManager.isRTL ? 'right' : 'left';
const styles = StyleSheet.create({
  outerCircle: {
    justifyContent: 'center',
    alignItems: 'center',
  },
  innerCircle: {
    overflow: 'hidden',
    justifyContent: 'center',
    alignItems: 'center',
  },
  leftWrap: {
    position: 'absolute',
    top: 0,
    [`${direction}`]: 0,
  },
  halfCircle: {
    position: 'absolute',
    top: 0,
    left: 0,
    borderTopRightRadius: 0,
    borderBottomRightRadius: 0,
  },
});

function percentToDegrees(percent) {
  return percent * 3.6;
}

export default class CircularProgress extends Component {
  static propTypes = {
    color: PropTypes.string,
    shadowColor: PropTypes.string,
    bgColor: PropTypes.string,
    radius: PropTypes.number.isRequired,
    borderWidth: PropTypes.number,
    percent: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types
    children: PropTypes.node,
    containerStyle: ViewPropTypesStyle,
    outerCircleStyle: ViewPropTypesStyle,
  };

  static defaultProps = {
    color: '#f00',
    shadowColor: '#999',
    bgColor: '#e9e9ef',
    borderWidth: 2,
    children: null,
    containerStyle: null,
  };

  constructor(props) {
    super(props);
    this.state = this.getInitialStateFromProps(props);
  }

  UNSAFE_componentWillReceiveProps(nextProps) {
    this.setState(this.getInitialStateFromProps(nextProps));
  }

  getInitialStateFromProps(props) {
    const percent = Math.max(Math.min(100, props.percent), 0);
    const needHalfCircle2 = percent > 50;
    let halfCircle1Degree;
    let halfCircle2Degree;
    // degrees indicate the 'end' of the half circle, i.e. they span (degree - 180, degree)
    if (needHalfCircle2) {
      halfCircle1Degree = 180;
      halfCircle2Degree = percentToDegrees(percent);
    } else {
      halfCircle1Degree = percentToDegrees(percent);
      halfCircle2Degree = 0;
    }

    return {
      halfCircle1Degree,
      halfCircle2Degree,
      halfCircle2Styles: {
        // when the second half circle is not needed, we need it to cover
        // the negative degrees of the first circle
        backgroundColor: needHalfCircle2 ? props.color : props.shadowColor,
      },
    };
  }

  renderHalfCircle(rotateDegrees, halfCircleStyles) {
    const {radius, color} = this.props;
    const key = I18nManager.isRTL ? 'right' : 'left';
    return (
      <View
        style={[
          styles.leftWrap,
          {
            width: radius,
            height: radius * 2,
          },
        ]}>
        <View
          style={[
            styles.halfCircle,
            {
              width: radius,
              height: radius * 2,
              borderRadius: radius,
              overflow: 'hidden',
              transform: [
                {translateX: radius / 2},
                {rotate: `${rotateDegrees}deg`},
                {translateX: -radius / 2},
              ],
              backgroundColor: color,
              ...halfCircleStyles,
            },
          ]}
        />
      </View>
    );
  }

  renderInnerCircle() {
    const radiusMinusBorder = this.props.radius - this.props.borderWidth;
    return (
      <View
        style={[
          styles.innerCircle,
          {
            width: radiusMinusBorder * 2,
            height: radiusMinusBorder * 2,
            borderRadius: radiusMinusBorder,
            backgroundColor: this.props.bgColor,
            ...this.props.containerStyle,
          },
        ]}>
        {this.props.children}
      </View>
    );
  }

  render() {
    const {
      halfCircle1Degree,
      halfCircle2Degree,
      halfCircle2Styles,
    } = this.state;
    return (
      <View
        style={[
          styles.outerCircle,
          {
            width: this.props.radius * 2,
            height: this.props.radius * 2,
            borderRadius: this.props.radius,
            backgroundColor: this.props.shadowColor,
            ...this.props.outerCircleStyle,
          },
        ]}>
        {this.renderHalfCircle(halfCircle1Degree)}
        {this.renderHalfCircle(halfCircle2Degree, halfCircle2Styles)}
        {this.renderInnerCircle()}
      </View>
    );
  }
}

Upvotes: 0

Ehsan Sarshar
Ehsan Sarshar

Reputation: 3211

it's the way that render engine paint two overlapped path. they just kind of antializing. you can solve it by increasing the width and height of the front circle just a little to tackle the issue or you can hide the one which is behind

Upvotes: 0

2pha
2pha

Reputation: 10155

It seems to me to be because of css box-sizing.
With the default on most browsers box-sizing: content-box;, padding and border-width are added to the height and width..
but with box-sizing: border-box;, padding and border-width are contained within the set width and height.
so..
you probably want to add box-sizing: border-box;.

I usually add it to my whole document with

html {
  box-sizing: border-box;
}
*, *:before, *:after {
  box-sizing: inherit;
}

Edit (after screenshot added)

The problem is not that one circle is smaller... it's just that the darker border is peeking through from underneath.

Here is example where you can change the colour and visibility to make what is wrong obvious. Run the snippet and check the second checkbox to quickly see it.

function colourChange(e) {
  var target = e.target.getAttribute('data-target');
  var style = e.target.getAttribute('data-style');
  var col = e.target.value;
  document.getElementById(target).style[style] = col;
}

function visChange(e) {
  var target = e.target.getAttribute('data-target');
  document.getElementById(target).style.display = (e.target.checked) ? 'block' : 'none';
}

function toFront(e) {
	var other = 'layer1';
	if (e.target.value == 'layer1') {
  	var other = 'layer2';
  }
  var otherz = document.getElementById(other).style.zIndex;
	var layer = document.getElementById(e.target.value);
	layer.style.zIndex = parseInt(otherz) + 1;
}

document.getElementById('border-col1').addEventListener('change', colourChange);
document.getElementById('border-col2').addEventListener('change', colourChange);
document.getElementById('background-col1').addEventListener('change', colourChange);
document.getElementById('background-col2').addEventListener('change', colourChange);
document.getElementById('vis1').addEventListener('change', visChange);
document.getElementById('vis2').addEventListener('change', visChange);
document.getElementById('front1').addEventListener('change', toFront);
document.getElementById('front2').addEventListener('change', toFront);
html {
  box-sizing: border-box;
}
*, *:before, *:after {
  box-sizing: inherit;
}

#container {
    flex: 1;
    background-color: white;
    align-items: center;
    justify-content: center;
  }
  #layer1 {
    width: 50px;
    height: 50px;
    border-width: 3px;
    border-radius: 25px;
    border-color: black;
    position: absolute;
    border-style: solid;
  }
  #layer2 {
    width: 50px;
    height: 50px;
    border-width: 3px;
    border-radius: 25px;
    border-color: white;
    position: absolute;
    border-style: solid;
  }
  
#inputs {
  position: absolute;
  top: 0px;
  right: 0px;
  padding: 10px;
}
<div id="container">
  <div id="layer1" style="z-index:1;"></div>
  <div id="layer2" style="z-index:2;"></div>
</div>
<div id="inputs">
  <div>
    border colours  
  </div>
  <input type="color" id="border-col1" data-target="layer1" data-style="borderColor" value="#000000">
  <input type="color" id="border-col2" data-target="layer2" data-style="borderColor" value="#ffffff">
  <div>
    background colours  
  </div>
  <input type="color" id="background-col1" data-target="layer1" data-style="backgroundColor" value="#ffffff">
  <input type="color" id="background-col2" data-target="layer2" data-style="backgroundColor" value="#ffffff">
  <div>
    visibility<br/>(display block / none)  
  </div>
  <input type="checkbox" id="vis1" data-target="layer1" checked>
  <input type="checkbox" id="vis2" data-target="layer2" checked>
  <div>
    in front  
  </div>
  <input type="radio" id="front1" name="front" value="layer1">
  <input type="radio" id="front2" name="front" value="layer2">
</div>

Upvotes: 1

Related Questions