Funky Image Positioning in react native? [duplicate] - css

This question already has answers here:
Image Alignment when using resizeMode = contain
(3 answers)
Closed 3 years ago.
I'm trying to create an image that fits snugly at the top of the screen of my device. Basically, a header image. At long last I've been able to scale the image to the width of the screen, but now that I have it, it always gives itself an extra top margin that I can't seem to get around. I've played around with flex properties but nothing seems to work! I was wondering if anyone had experienced an issue like this before. I'm a bit new to CSS and such so forgive me if my question is dumb.
Here is what appears (the margin is well above 25 pixels):
https://imgur.com/a/9Mzql
My code is as follows (simplified for you all):
import React, { Component } from 'react';
import {
View,
Image,
StyleSheet,
} from 'react-native';
export class Home extends Component {
static NavOption = {
title: 'Home',
};
render() {
return (
<View>
<View style={{flex: 1, justifyContent: 'flex-start'}}>
<Image
source={require('./pictures/my-image.png')}
style={styles.headerImage}
resizeMode='contain'
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
headerImage: {
maxWidth: '100%',
},
});

resizeMode='contain' actually centers and pads the image based on how it's resized. You will have to calculate and set the height dynamically.
Here is a good example of how that can be done:
Image Alignment when using resizeMode = contain

Gordon, not a dumb question. I am having the exact same issue. I did what ReyHaynes suggested (thanks!) but in my case, modified the example he linked to for my situation:
original example
The example expects a static maxWidth/maxHeight to return a set of dimensions for your image. Since i didn't know what those vars needed to be, here's how i modified his example to work for my situation:
import { Dimensions } from 'react-native';
then, change this...
var maxWidth = 110;
var maxHeight = 30;
to...
var maxWidth = Dimensions.get('window').width;
var maxHeight = height;
Finally, follow the original example and implement as he suggested.

Related

How to center items inside konvajs?

I am currently building some sort of meme-editor in react and for that I am using konvajs to function similar to a Canvas.
My problem is that I am unable to center the items inside the canva stage, as there seems to be some property that just overrides my styling.
This is (part of) the return statement in my react-component:
<div className="mycanvas">
<Stage width={500} height={500} className="stage">
<Layer>
<Image image={image} className="meme" />
{textFields.map((text) => (
<Text
text={text}
draggable
fontFamily="Impact"
fontSize="30"
stroke="white"
/>
))}
</Layer>
</Stage>
</div>
And this is how the output gets rendered.
I have coloured the background of the wrapper blue, to show in which box the image should be centered.
I have already tried CSS on the classes "mycanvas", "stage" and "meme" and also on "konvajs-content" (as that showed up in my inspector for some reason). I have used align-items: center, margin: auto and a couple others, but I think normal CSS does not really apply here.
I think it is an issue regarding the generaly styling of konvajs components, but unfortunately I could not find any solution on stackoverflow or the konva documentation.
This is an instance where CSS can't help. When the image is applied to the canvas using its height and width at the x and y coordinates you supply, the pixels of the image become part of the rasterized canvas. In other words, the image doesn't exist independent of the canvas.
Therefore, if you want to center the image inside of your canvas, you need to do a little math to calculate the x and y coordinates that will place the image centered inside the canvas.
Demo
For example, if your canvas size is 500px tall and your image has a height of 350px, then you need to set the y position to 75px (i.e., (500 - 350) / 2).
The demo code below shows how to replicate the behavior of CSS object-fit: contain. This will adjust the image to fill the canvas in one direction, and then center the image in the other direction.
import { useState, useEffect } from "react";
import { Stage, Layer, Image, Text } from "react-konva";
function Example() {
const w = window.innerWidth;
const h = window.innerHeight;
const src = "https://konvajs.org/assets/yoda.jpg";
const [image, setImage] = useState(null);
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const image = new window.Image();
image.src = src;
image.addEventListener("load", handleLoad);
function handleLoad(event) {
const image = event.currentTarget;
/* after the image is loaded, you can get it's dimensions */
const imgNaturalWidth = image.width;
const imgNaturalHeight = image.height;
/*
calculate the horizontal and vertical ratio of the
image dimensions versus the canvas dimensions
*/
const hRatio = w / imgNaturalWidth;
const vRatio = h / imgNaturalHeight;
/*
to replicate the CSS Object-Fit "contain" behavior,
choose the smaller of the horizontal and vertical
ratios
if you want a "cover" behavior, use Math.max to
choose the larger of the two ratios instead
*/
const ratio = Math.min(hRatio, vRatio);
/*
scale the image to fit the canvas
*/
image.width = imgNaturalWidth * ratio;
image.height = imgNaturalHeight * ratio;
/*
calculate the offsets so the image is centered inside
the canvas
*/
const xOffset = (w - image.width) / 2;
const yOffset = (h - image.height) / 2;
setPos({
x: xOffset,
y: yOffset
});
setImage(image);
}
return () => {
image.removeEventListener("load", handleLoad);
};
}, [src, h, w]);
return (
<Stage width={w} height={h} style={{ background: "black" }}>
<Layer>
<Image x={pos.x} y={pos.y} image={image} />
<Text
text="I am centered"
fontFamily="Impact"
fontSize={50}
stroke="white"
strokeWidth={1}
x={pos.x}
y={pos.y}
/>
</Layer>
</Stage>
);
}

How to show only one Item at a time in React native's FlatLists?

I would really like to make this design but I don't know how to implement it in code, any suggestions?The issue is:I don't know how to show only one item at a time on the FlatList and how to make the centered item look closer.Lastly,How can I make the scroll go from one item to the other,without beeing sort of in the middle?
I tried looking online but I haven't been able to find what I need, if you found something please let me know.
I think you are looking for a carousel. Please refer to this library.
A list that allows both dragging and pressing a button.
For that i would use a horizontal FlatList. Then, make each item in the list take up 100% of the screen width and use the FlatList's ref param to scroll to the next item in the list when pressing a button and have a useState to keep track of which index the user is at to align button and swiping.
Heres how i would do it.
Get 100% screen width
import { Dimensions } from 'react-native';
const { width } = Dimensions.get('window');
Set up horizontal FlatList
The key here is the params: pagingEnabled and horizontal.
onViewableItemsChanged is called whenever focused item changes.
useCallback is for optimization. import it with useState
// Method passed to FlatList. Displays the data's text.
const renderItem = useCallback(
({ item }) => (
<View style={[styles.card, { width }]}>
<Text style={styles.cardItem}>{item.text}</Text>
</View>
),
[]
);
<FlatList
ref={flatListRef} // Reference to list. Used to scroll to specific items.
horizontal
pagingEnabled // cause snapping to items
data={data}
renderItem={renderItem}
onViewableItemsChanged={onScroll} // Calling with anonymous function here causes issues
viewabilityConfig={{
itemVisiblePercentThreshold: 100,
}}
/>
Button
Using the flatListRef and my useState, to scroll to the next index and keep track of where the user is at.
import React, { useState, useRef, useCallbac } from 'react';
const flatListRef = useRef(null);
const [currentSectionIndex, setCurrentSectionIndex] = useState(0);
const onButtonPress = useCallback(() => {
if (currentSectionIndex === data.length - 1) {
// What you want to do at the end of the list.
} else if (flatListRef.current) {
// Go to the next item
flatListRef.current.scrollToIndex({
index: currentSectionIndex + 1,
});
setCurrentSectionIndex(currentSectionIndex + 1);
}
}, [currentSectionIndex, data.length]);
OnScroll function
We wanted to allow dragging and pressing a button to go move on in the list.
So when the user scrolls, we use this function, which we passed to the FlatLists onViewableItemsChanged
// When scrolling set useState to keep track of where the user is at.
const onScroll = useCallback(({ viewableItems }) => {
if (viewableItems.length === 1) {
setCurrentSectionIndex(viewableItems[0].index);
}
}, []);

How to find correct values for width, height and viewBox with react-native-svg

I have been trying to do something that seemed easy, but I have been trying for a few hours and I can't find the solution.
I have an SVG that needs to be on top of a screen. It came from the designer with these dimensions:
<Svg width="354px" height="190px" viewBox="0 0 354 190">...</Svg>
In React Native, that would go inside of a container, and the SVG needs to take the full width of the screen, which I am taking from:
Dimensions.get("window").width
My problem is, I haven't found a way to scale the SVG to take 100% of the screen width, finding out the correct height (or a way for it to be set automatically), and preserve the aspect ratio. I've tried like a million things, including playing around with the container's aspectRatio style and its height (or not setting the height at all). Whenever I've found some "proportions" that worked, I tried in a different device with different screen width and it didn't look good at all (cropped, smaller than the screen's width, etc).
I feel like the preserveAspectRatio property in the SVG (react-native-svg) is somehow conflicting with the aspectRatio style. And I am totally lost with the preserveAspectRatio, I haven't found a way to make it scale without being cropped.
Does anyone have any idea how to achieve this?
This is my final code, which returns a HeatMap component showing an SVG, but although it has the correct height, part of the SVG is out of the screen from the right (looks cropped because it's too wide):
const windowWidth = Dimensions.get("window").width;
const getSVGRootProps = ({ width, height }) => ({
width: "100%",
height: "100%",
viewBox: `0 0 ${width} ${height}`,
preserveAspectRatio: "xMinYMin meet",
});
const FieldShape = () => {
const width = 354; // Original width
const height = 190; // Original height
const aspectRatio = width / height;
// adjusted height = <screen width> * original height / original width
const calculatedHeight = (windowWidth * height) / width;
const fieldStyles = {
width: windowWidth,
height: calculatedHeight,
aspectRatio,
};
return (
<View style={fieldStyles}>
<Svg {...getSVGRootProps({ windowWidth, calculatedHeight })}>
...
</Svg>
</View>
);
};
const HeatMap = () => {
return <FieldShape />;
};
This is the result:
I've found the solution, and I am posting it here in case anyone runs into the same problem with react native and SVG. Basically, if you're trying to get an SVG file and turn it into a component with "dynamic" parts (like programmatically set colors to path based on data, or display SVG text), you'll probably run into this issue.
What I did was to use SVGR to convert the original SVG into a react native component (with react-native-svg). Then I just replaced hardcoded data with variables (from props) as needed. It looked good, but I had a problem with the component's size. I couldn't find a consistent way to display it across different device sizes and resolutions. It seemed easy, but I tried for hours and the results were different on each screen size. After asking here and opening an issue on react-native-svg repo, I got no answers and no clues (not blaming anyone, just saying it was maybe not something a lot of people runs into). So I digged and digged and I finally found this post by Lea Verou, where she talked about absolute and relative SVG paths. That made me think that maybe I was having so many issues trying to find the perfect resizing formula because my paths weren't relative, but absolute. So I tried this jsfiddle by heyzeuss, pasting my (original) SVG code, and then copying the results. I pasted the results into this handy SVGR playground (SVG to JS) tool, and then I changed some bits to achieve my goal:
I want my SVG to take the full screen's width, width its height scaled accordingly.
So this is what I changed:
// SVG's original size is 519 width, 260 height
// <Svg width="519" height="260" viewBox="0 0 519 260">...</Svg>
// I also added a container, which enforces the aspect ratio
const originalWidth = 519;
const originalHeight = 260;
const aspectRatio = originalWidth / originalHeight;
const windowWidth = Dimensions.get("window").width;
return (
<View style={{ width: windowWidth, aspectRatio }}>
<Svg
width="100%"
height="100%"
viewBox={`0 0 ${originalWidth} ${originalHeight}`}>
...
</Svg>
</View>
)
I learned some things while doing this, for example, that there's a #svgr/cli included in create-react-app and also available in my react-native project without installing anything extra, so it must be bundled with the original dependencies too. You can run this command and it'll turn a single file or all files in a folder from .svg to React components:
npx #svgr/cli [-d out-dir] [--ignore-existing] [src-dir]
The script used to transform absolute paths to relatives is part of this library called Snap.svg, but you'll only need like a 1% of it (Snap.path.toRelative). I am thinking of having a small tool that would take all the paths in an svg file, and apply this transformation. To be totally honest, I have been dealing with SVG files for years but I never really had a proper look at how it works internally, the format of the paths, coordinates, and so on, so this has been highly instructive :)
I hope this helps you if you find yourself in the same situation!
Expanding on Luis Serrano answer, you can leave out windowWidth and use 100%.
Example:
import * as React from 'react';
import Svg, { Circle, Path } from 'react-native-svg';
import { View } from 'react-native';
export default function SvgExample() {
const originalWidth = 500;
const originalHeight = 500;
const aspectRatio = originalWidth / originalHeight;
return (
<View style={{ width: "100%", aspectRatio, backgroundColor: "salmon" }}>
<Svg width="100%" height="100%" viewBox={`0 0 ${originalWidth} ${originalHeight}`}>
<Circle cx="250" cy="250" r="40" fill="yellow" />
<Circle cx="240" cy="240" r="4" fill="black" />
<Circle cx="260" cy="240" r="4" fill="black" />
<Path d="M 240 265 A 20 20 0 0 0 260 260" strokeWidth={2} stroke="black" />
</Svg>
</View>
)
}
This has the advantage that it respects the padding of the enclosing View.
import { StyleSheet, View } from 'react-native';
import SvgExample from './SvgExample';
export default function TabOneScreen() {
return (
<View style={styles.container}>
<SvgExample/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
padding: 20,
},
});
Thanks to you both guys Luis Serrano & Phierru!
I put this whole thing into an example Snack.
SNACK:
https://snack.expo.dev/#changnoi69/fbf937
When you change the marginLeft and marginRight of that view that is wrapped around the SVG-Component the SVG resizes according to it.
<View style={{marginLeft:"20%", marginRight:"20%", backgroundColor: "pink"}}>
<NoInternetConnectionSVG />
</View>

render objects in specific position with parametrize in react native

so i want to create an object, a message boxes that will be rendered on the screen in a relative position in the container.
i know to use position: 'relative' or 'absolute' and use the top and left values, but i want in addition to get the specific coordinates from the container component.
each instance of message box i want to render in a specific location retrieved from the container.
in general i cant find something in react native that fit my needs... im trying to make an app with managing objects on the screen in absolute position, all the algorithm where to render and how to move will be managed by me, but i cant find a good way to do that in react native seems like all the styles are better suit for relative and rendering obejcts by order design, someone has any tips?
enter code here:
export default class Message extends Component {
constructor(props) {
super(props)
};
render() {
var position = StyleSheet.create({top: this.props.top,
left: this.props.left});
var absoluteCircle = StyleSheet.flatten([styles.circle,
position]);
return (
<View style={absoluteCircle}>
<Text style={styles.circleText}>{this.props.bubbleText}
</Text>
</View>
);
}
}

React Native - Move Button on Tap

I'm new to React Native (experiences iOS developer in Swift) and I can't seem to find anything online for how to move UI elements in response to touch events. (Or maybe I'm just really bad at searching stack overflow lol).
Eventually, I want to have a textbox in the center of the screen, and when the user begins to type (or taps on the box to start typing), the box will slide to the top of the screen. However I can't even find a simple tutorial for this. I know that I can have a const style that defines style/position aspects of the textbox, and I can create a second style and then on button tap I can change the textbox's style and re-render, but it seems overkill to make an entire second style, where only one attribute is changing.
Can someone provide sample React Native code, where there is a text label and a button on the screen, and when the user taps the button, the label moves from above the button to below the button?
Check out this example of moving an element on touch. I use a <TouchableWithoutFeedback> and onPressIn. When you touch it, it changes the x and y position of it:
https://snack.expo.io/#noitsnack/onpressin-twice
import React, { Component } from 'react';
import { Text, View, TouchableWithoutFeedback, Image, Dimensions } from 'react-native';
import IMAGE_EXPO from './assets/expo.symbol.white.png'
const IMAGE_SIZE = 100;
export default class App extends Component {
state = {
score: 0,
...getRandXY()
}
render() {
const { score, x, y } = this.state;
return (
<View style={{flex:1, backgroundColor:'steelblue' }}>
<Text style={{fontSize:72, textAlign:'center'}}>{score}</Text>
<TouchableWithoutFeedback onPressIn={this.addScore}>
<Image source={IMAGE_EXPO} style={{ width:IMAGE_SIZE, height:IMAGE_SIZE, left:x, top:y, position:'absolute' }} />
</TouchableWithoutFeedback>
</View>
)
}
addScore = e => {
this.setState(({ score }) => ({ score:score+1, ...getRandXY() }));
}
}
function getRandXY() {
return {
x: getRandInt(0, Dimensions.get('window').width - IMAGE_SIZE),
y: getRandInt(0, Dimensions.get('window').height - IMAGE_SIZE)
}
}
function getRandInt(min, max)
{
return Math.floor(Math.random()*(max-min+1)+min);
}
Once you get this movement down you can move to the Animation API to get teh sliding effect.

Resources