Different handling of aspect ratio on flexible items compared to CSS #1418

Open
opened 2023-10-11 14:46:35 -07:00 by gaearon · 3 comments
gaearon commented 2023-10-11 14:46:35 -07:00 (Migrated from github.com)

Description

Here's the reproducing code:

import { View, ScrollView } from 'react-native';


export default function App() {
  return (
    <ScrollView>
      <View style={{flex: 1, }}>
        <View style={{ flexDirection: 'row', gap: 5, backgroundColor: 'red' }}>
          <View style={{ flex: 2, aspectRatio: 1 }}>
            <View style={{ backgroundColor: 'blue', width: '100%', height: '100%'}} />
          </View>
          <View style={{ flex: 1, gap: 5 }}>
            <View style={{ flex: 1, aspectRatio: 1 }}>
              <View style={{ backgroundColor: 'blue', width: '100%', height: '100%' }} />
            </View>
            <View style={{ flex: 1, aspectRatio: 1 }}>
              <View style={{ backgroundColor: 'blue', width: '100%', height: '100%' }} />
            </View>
          </View>
        </View> 
      </View>
    </ScrollView>
  );
}

On the web, gap works as expected (no red line at the bottom):

Screenshot 2023-10-11 at 22 43 54

On both Android and iOS, there is a red line at the bottom:

Screenshot 2023-10-11 at 22 44 26

It seems like nested gap properties combined with aspectRatio and flex don't work correctly inside ScrollView.

Interesting to note:

  • Removing ScrollView fixes it
  • Replacing flex with flexGrow on the immediate children of flexDirection: 'row', also fixes it (but this would break the web so has to be conditional)

React Native Version

0.72.5

Output of npx react-native info

System:
OS: macOS 13.2.1
CPU: (10) arm64 Apple M1 Max
Memory: 6.23 GB / 64.00 GB
Shell:
version: 5.8.1
path: /bin/zsh
Binaries:
Node:
version: 18.17.1
path: /usr/local/bin/node
Yarn:
version: 1.22.19
path: /usr/local/bin/yarn
npm:
version: 9.6.7
path: /usr/local/bin/npm
Watchman:
version: 2023.01.30.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.11.3
path: /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 22.4
- iOS 16.4
- macOS 13.3
- tvOS 16.4
- watchOS 9.4
Android SDK: Not Found
IDEs:
Android Studio: 2022.1 AI-221.6008.13.2211.9514443
Xcode:
version: 14.3.1/14E300c
path: /usr/bin/xcodebuild
Languages:
Java:
version: javac 18
path: /usr/bin/javac
Ruby:
version: 2.7.6
path: /Users/dan/.rbenv/shims/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.2.0
wanted: 18.2.0
react-native:
installed: 0.72.5
wanted: 0.72.5
react-native-macos: Not Found
npmGlobalPackages:
"react-native": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: true
newArchEnabled: false

Steps to reproduce

See above.

https://snack.expo.dev/WvNJKiWAi

### Description Here's the reproducing code: ```js import { View, ScrollView } from 'react-native'; export default function App() { return ( <ScrollView> <View style={{flex: 1, }}> <View style={{ flexDirection: 'row', gap: 5, backgroundColor: 'red' }}> <View style={{ flex: 2, aspectRatio: 1 }}> <View style={{ backgroundColor: 'blue', width: '100%', height: '100%'}} /> </View> <View style={{ flex: 1, gap: 5 }}> <View style={{ flex: 1, aspectRatio: 1 }}> <View style={{ backgroundColor: 'blue', width: '100%', height: '100%' }} /> </View> <View style={{ flex: 1, aspectRatio: 1 }}> <View style={{ backgroundColor: 'blue', width: '100%', height: '100%' }} /> </View> </View> </View> </View> </ScrollView> ); } ``` On the web, `gap` works as expected (no red line at the bottom): <img width="355" alt="Screenshot 2023-10-11 at 22 43 54" src="https://github.com/facebook/react-native/assets/810438/6ec5345e-51b0-43a7-b8c6-af2b7009fe0d"> On both Android and iOS, there is a red line at the bottom: <img width="330" alt="Screenshot 2023-10-11 at 22 44 26" src="https://github.com/facebook/react-native/assets/810438/8fd06eba-056e-426e-9d49-c335b5a2d462"> It seems like nested `gap` properties combined with `aspectRatio` and `flex` don't work correctly inside `ScrollView`. Interesting to note: - Removing `ScrollView` fixes it - Replacing `flex` with `flexGrow` on the immediate children of `flexDirection: 'row',` also fixes it (but this would break the web so has to be conditional) ### React Native Version 0.72.5 ### Output of `npx react-native info` System: OS: macOS 13.2.1 CPU: (10) arm64 Apple M1 Max Memory: 6.23 GB / 64.00 GB Shell: version: 5.8.1 path: /bin/zsh Binaries: Node: version: 18.17.1 path: /usr/local/bin/node Yarn: version: 1.22.19 path: /usr/local/bin/yarn npm: version: 9.6.7 path: /usr/local/bin/npm Watchman: version: 2023.01.30.00 path: /opt/homebrew/bin/watchman Managers: CocoaPods: version: 1.11.3 path: /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms: - DriverKit 22.4 - iOS 16.4 - macOS 13.3 - tvOS 16.4 - watchOS 9.4 Android SDK: Not Found IDEs: Android Studio: 2022.1 AI-221.6008.13.2211.9514443 Xcode: version: 14.3.1/14E300c path: /usr/bin/xcodebuild Languages: Java: version: javac 18 path: /usr/bin/javac Ruby: version: 2.7.6 path: /Users/dan/.rbenv/shims/ruby npmPackages: "@react-native-community/cli": Not Found react: installed: 18.2.0 wanted: 18.2.0 react-native: installed: 0.72.5 wanted: 0.72.5 react-native-macos: Not Found npmGlobalPackages: "*react-native*": Not Found Android: hermesEnabled: true newArchEnabled: false iOS: hermesEnabled: true newArchEnabled: false ### Steps to reproduce See above. ### Snack, screenshot, or link to a repository https://snack.expo.dev/WvNJKiWAi
intergalacticspacehighway commented 2023-10-11 22:35:44 -07:00 (Migrated from github.com)

@NickGerleman spent some time digging into this. The issue is how the combination of aspectRatio + alignItems stretch is being treated in browser vs yoga. Aspect ratio is initially used to calculate the dimension in both browser and yoga but if the align items is set to stretch then the child will grow to the cross axis flex line dimension in browser whereas in yoga it'll always respect the aspect ratio. In the below line, if we don't consider aspect ratio when align items is stretch, we get the same result in browser and yoga.
bac658b4f5/yoga/algorithm/CalculateLayout.cpp (L1886)

Try below snippet without gap. It also varies between browser and yoga. I can also see some more inconsistencies in margin and aspect ratio behavior. There is some issue in how aspect ratio is treated compared to browser. I'll dig with the specs and update when I have more findings.

<View style={{ flex: 1, overflow: "scroll" }}>
  <View style={{ flexDirection: "row", backgroundColor: "red" }}>
    <View
      style={{
        flex: 2,
        aspectRatio: 1,
        backgroundColor: "blue",
      }}
    />
    <View style={{ width: 5 }} />
    <View style={{ flex: 1 }}>
      <View
        style={{
          flex: 1,
          aspectRatio: 1,
          backgroundColor: "blue",
        }}
      />
      <View style={{ height: 5 }} />
      <View style={{ flex: 1, aspectRatio: 1, backgroundColor: "blue" }} />
    </View>
  </View>
</View>;
@NickGerleman spent some time digging into this. The issue is how the combination of aspectRatio + alignItems stretch is being treated in browser vs yoga. Aspect ratio is initially used to calculate the dimension in both browser and yoga but if the align items is set to stretch then the child will grow to the cross axis flex line dimension in browser whereas in yoga it'll always respect the aspect ratio. In the below line, if we don't consider aspect ratio when align items is stretch, we get the same result in browser and yoga. https://github.com/facebook/yoga/blob/bac658b4f56026c7f15a502e7a5820364761d51c/yoga/algorithm/CalculateLayout.cpp#L1886 Try below snippet without gap. It also varies between browser and yoga. I can also see some more inconsistencies in margin and aspect ratio behavior. There is some issue in how aspect ratio is treated compared to browser. I'll dig with the specs and update when I have more findings. ```jsx <View style={{ flex: 1, overflow: "scroll" }}> <View style={{ flexDirection: "row", backgroundColor: "red" }}> <View style={{ flex: 2, aspectRatio: 1, backgroundColor: "blue", }} /> <View style={{ width: 5 }} /> <View style={{ flex: 1 }}> <View style={{ flex: 1, aspectRatio: 1, backgroundColor: "blue", }} /> <View style={{ height: 5 }} /> <View style={{ flex: 1, aspectRatio: 1, backgroundColor: "blue" }} /> </View> </View> </View>; ```
NickGerleman commented 2023-10-12 05:30:57 -07:00 (Migrated from github.com)

@intergalacticspacehighway great work, as always 🙂.

@nicoburns helpfully created https://github.com/facebook/yoga/issues/1295 a while back with a set of fixtures used by Taffy for aspect ratio, showing differences between Yoga and browser behavior. The story here being that aspectRatio in Yoga predates the aspectRatio property in CSS.

I will retitle this issue for clarity, since it seems like we think the root issue here is around Yogas handling of aspect ratio for flexible items (at least for stretched cross axis).

@intergalacticspacehighway great work, as always 🙂. @nicoburns helpfully created https://github.com/facebook/yoga/issues/1295 a while back with a set of fixtures used by Taffy for aspect ratio, showing differences between Yoga and browser behavior. The story here being that aspectRatio in Yoga predates the aspectRatio property in CSS. I will retitle this issue for clarity, since it seems like we think the root issue here is around Yogas handling of aspect ratio for flexible items (at least for stretched cross axis).
gaearon commented 2023-10-12 11:04:13 -07:00 (Migrated from github.com)

Related, even with the workaround I've posted (flexGrow), if you apply borderRadius to images, you'll notice they slightly go outside the container (the border radius gets cut off at the right bottom). I've ended up refactoring my workaround to use margins instead for this reason.

Related, even with the workaround I've posted (`flexGrow`), if you apply borderRadius to images, you'll notice they slightly go outside the container (the border radius gets cut off at the right bottom). I've ended up refactoring my workaround to use margins instead for this reason.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DaddyFrosty/yoga#1418
No description provided.