27. Uso de dimensiones relativas

 

Tutorial: Uso de Dimensiones Relativas en React Native

📱 Introducción a las Dimensiones Relativas

Las dimensiones relativas son valores que se calculan en función del tamaño de la pantalla del dispositivo, permitiendo que nuestros componentes se adapten automáticamente a diferentes resoluciones y densidades de píxeles.

Problema: Los valores absolutos en píxeles (ej: width: 100, fontSize: 20) se ven diferentes en dispositivos con distintas densidades de pantalla.

Solución: Usar unidades relativas que se escalan según el tamaño de la pantalla.


🎯 Objetivo del Tutorial

Aprender a crear un sistema de dimensiones relativas para:

  1. Evitar código repetitivo con múltiples if-else

  2. Homologar tamaños en diferentes dispositivos

  3. Mantener un código más limpio y mantenible


🔧 Paso 1: El Problema del Código Repetitivo

Código inicial (ineficiente):

tsx

import React from 'react';

import { View, Text, Image, Dimensions, StyleSheet } from 'react-native';


const { width: SCREEN_WIDTH } = Dimensions.get('window');


export const Profile = () => {

  // ❌ Múltiples funciones con lógica repetitiva

  const getFontSize = () => {

    if (SCREEN_WIDTH < 350) return 12;

    if (SCREEN_WIDTH < 400) return 14;

    if (SCREEN_WIDTH < 450) return 18;

    return 20;

  };


  const getPictureContainerSize = () => {

    // Lógica similar para contenedor de imagen

    return 100;

  };


  const getPictureSize = () => {

    // Lógica similar para imagen

    return 75;

  };


  // ❌ Llamadas repetidas a las funciones

  const pictureContainerSize = getPictureContainerSize();

  const pictureSize = getPictureSize();


  return (

    <View style={styles.container}>

      <Text style={[styles.title, { fontSize: getFontSize() }]}>

        PERFIL

      </Text>

      

      <View style={[

        styles.imageContainer,

        { 

          width: pictureContainerSize,

          height: pictureContainerSize 

        }

      ]}>

        <Image

          source={{ uri: 'https://ejemplo.com/imagen.jpg' }}

          style={[

            styles.image,

            { width: pictureSize, height: pictureSize }

          ]}

        />

      </View>

    </View>

  );

};

Problemas:

  1. ✅ Código repetitivo para cada dimensión

  2. ✅ Difícil de mantener

  3. ✅ Ineficiente (cálculos en cada render)


🚀 Paso 2: Crear una Función de Escala Universal

1. Crear el archivo de utilidades:

text

src/

├── utils/

│   └── scaleUtils.ts

└── screens/

    └── Profile.tsx

2. Implementar scaleUtils.ts:

typescript

// src/utils/scaleUtils.ts

import { Dimensions } from 'react-native';


// Obtener dimensiones de la pantalla

const { width: SCREEN_WIDTH } = Dimensions.get('window');


// Ancho base de referencia (ej: iPhone 6/7/8)

const BASE_WIDTH = 380;


// Factor de escala (similar a "rem" en CSS)

const SCALE_FACTOR = SCREEN_WIDTH / BASE_WIDTH;


/**

 * Convierte píxeles a unidades escaladas relativas

 * @param pixels - Valor en píxeles para el ancho base

 * @returns Valor escalado según el tamaño de pantalla

 */

export const getScale = (pixels: number): number => {

  return pixels * SCALE_FACTOR;

};


/**

 * Escala vertical (para alturas)

 * @param pixels - Valor en píxeles

 * @returns Valor escalado verticalmente

 */

export const getVerticalScale = (pixels: number): number => {

  const { height: SCREEN_HEIGHT } = Dimensions.get('window');

  const BASE_HEIGHT = 800; // Altura base de referencia

  return pixels * (SCREEN_HEIGHT / BASE_HEIGHT);

};


/**

 * Escala moderada con factor de reducción

 * @param pixels - Valor en píxeles

 * @param factor - Factor de reducción (0-1, default 0.5)

 * @returns Valor escalado moderadamente

 */

export const getModerateScale = (

  pixels: number, 

  factor: number = 0.5

): number => {

  return pixels + (getScale(pixels) - pixels) * factor;

};


// Exportar dimensiones para uso directo

export const screenWidth = SCREEN_WIDTH;

export const screenHeight = Dimensions.get('window').height;


💡 Paso 3: Aplicar Dimensiones Relativas en Componentes

Profile.tsx optimizado:

tsx

import React from 'react';

import { View, Text, Image, StyleSheet } from 'react-native';

import { getScale, getVerticalScale } from '../utils/scaleUtils';


export const Profile = () => {

  // ✅ Usar funciones de escala una vez

  const pictureContainerSize = getScale(100);   // Relativo al ancho

  const pictureSize = getScale(75);            // Relativo al ancho

  const titleFontSize = getScale(20);          // Fuente escalada


  return (

    <View style={styles.container}>

      {/* Título con fuente escalada */}

      <Text style={[styles.title, { fontSize: titleFontSize }]}>

        PERFIL

      </Text>

      

      {/* Contenedor de imagen escalado */}

      <View style={[

        styles.imageContainer,

        { 

          width: pictureContainerSize,

          height: pictureContainerSize 

        }

      ]}>

        {/* Imagen escalada */}

        <Image

          source={{ uri: 'https://ejemplo.com/imagen.jpg' }}

          style={[

            styles.image,

            { 

              width: pictureSize,

              height: pictureSize 

            }

          ]}

        />

      </View>

    </View>

  );

};


// ✅ Estilos base sin dimensiones absolutas

const styles = StyleSheet.create({

  container: {

    flex: 1,

    backgroundColor: 'white',

    alignItems: 'center',

    paddingHorizontal: getScale(16),    // Padding relativo

    paddingVertical: getVerticalScale(18), // Padding vertical relativo

  },

  title: {

    fontWeight: '900',

    textTransform: 'uppercase',

    letterSpacing: getScale(4),         // Espaciado relativo

    marginBottom: getVerticalScale(30), // Margen relativo

  },

  imageContainer: {

    backgroundColor: '#E27683',

    borderRadius: getScale(50),         // Border radius relativo

    justifyContent: 'center',

    alignItems: 'center',

    marginBottom: getVerticalScale(40),

  },

  image: {

    borderRadius: getScale(37.5),       // Border radius relativo

  },

});


export default Profile;


📊 Paso 4: Comprender Cómo Funciona el Escalado

Ejemplo de cálculo:

typescript

// Dispositivo 1: Pantalla pequeña (320px de ancho)

const SCREEN_WIDTH_SMALL = 320;

const SCALE_FACTOR_SMALL = 320 / 380 = 0.842;


getScale(100) = 100 * 0.842 = 84.2


// Dispositivo 2: Pantalla grande (414px de ancho)

const SCREEN_WIDTH_LARGE = 414;

const SCALE_FACTOR_LARGE = 414 / 380 = 1.089;


getScale(100) = 100 * 1.089 = 108.9


// Resultado: El componente se escala proporcionalmente

Visualización del efecto:

text

Dispositivo Pequeño (320px)   Dispositivo Grande (414px)

┌─────────────────┐          ┌─────────────────────┐

│                 │          │                     │

│    [84px]       │          │    [109px]         │

│                 │          │                     │

└─────────────────┘          └─────────────────────┘


🔄 Paso 5: Escalado con Porcentajes

Alternativa usando porcentajes:

tsx

import React from 'react';

import { View, Text, Image, Dimensions, StyleSheet } from 'react-native';


const { width: SCREEN_WIDTH } = Dimensions.get('window');


export const Profile = () => {

  return (

    <View style={styles.container}>

      {/* Usar porcentaje del ancho de pantalla */}

      <View style={[

        styles.imageContainer,

        { 

          width: SCREEN_WIDTH * 0.25, // 25% del ancho

          height: SCREEN_WIDTH * 0.25 // Mantener relación 1:1

        }

      ]}>

        <Image

          source={{ uri: 'https://ejemplo.com/imagen.jpg' }}

          style={[

            styles.image,

            { 

              width: SCREEN_WIDTH * 0.1875, // 18.75% del ancho

              height: SCREEN_WIDTH * 0.1875 

            }

          ]}

        />

      </View>

    </View>

  );

};


// Calcular porcentajes dinámicamente

export const getPercentage = (percent: number): number => {

  return SCREEN_WIDTH * (percent / 100);

};


// Uso: getPercentage(25) = 25% del ancho


🎨 Paso 6: Sistema Completo de Dimensiones Relativas

Archivo completo responsiveUtils.ts:

typescript

// src/utils/responsiveUtils.ts

import { Dimensions, PixelRatio, Platform } from 'react-native';


const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');


// Basado en diseño para iPhone 11 (414x896)

const DESIGN_WIDTH = 414;

const DESIGN_HEIGHT = 896;


// Factor de escala horizontal

const scale = (size: number): number => 

  (SCREEN_WIDTH / DESIGN_WIDTH) * size;


// Factor de escala vertical  

const verticalScale = (size: number): number => 

  (SCREEN_HEIGHT / DESIGN_HEIGHT) * size;


// Escala moderada (recomendada para la mayoría de casos)

const moderateScale = (size: number, factor: number = 0.5): number => 

  size + (scale(size) - size) * factor;


// Escala basada en densidad de píxeles

const fontScale = (size: number): number => {

  const scaleFactor = SCREEN_WIDTH / DESIGN_WIDTH;

  const newSize = size * scaleFactor;

  

  if (Platform.OS === 'ios') {

    return Math.round(PixelRatio.roundToNearestPixel(newSize));

  }

  

  return Math.round(PixelRatio.roundToNearestPixel(newSize)) - 2;

};


// Detectar tipo de dispositivo

const isSmallDevice = SCREEN_WIDTH < 350;

const isMediumDevice = SCREEN_WIDTH >= 350 && SCREEN_WIDTH < 400;

const isLargeDevice = SCREEN_WIDTH >= 400;

const isTablet = SCREEN_WIDTH >= 768;


// Obtener porcentaje del ancho

const widthPercentage = (percent: number): number =>

  (SCREEN_WIDTH * percent) / 100;


// Obtener porcentaje del alto

const heightPercentage = (percent: number): number =>

  (SCREEN_HEIGHT * percent) / 100;


export default {

  scale,

  verticalScale,

  moderateScale,

  fontScale,

  isSmallDevice,

  isMediumDevice,

  isLargeDevice,

  isTablet,

  widthPercentage,

  heightPercentage,

  screenWidth: SCREEN_WIDTH,

  screenHeight: SCREEN_HEIGHT,

};


📱 Paso 7: Implementación en Proyecto Real

Componente completamente responsivo:

tsx

import React from 'react';

import { View, Text, Image, TextInput, StyleSheet } from 'react-native';

import Responsive from '../utils/responsiveUtils';


const Profile = () => {

  return (

    <View style={styles.container}>

      {/* Título responsivo */}

      <Text style={[

        styles.title,

        { fontSize: Responsive.fontScale(24) }

      ]}>

        PERFIL

      </Text>


      {/* Imagen responsiva */}

      <View style={[

        styles.imageContainer,

        {

          width: Responsive.scale(100),

          height: Responsive.scale(100),

          borderRadius: Responsive.scale(50),

        }

      ]}>

        <Image

          style={[

            styles.image,

            {

              width: Responsive.scale(75),

              height: Responsive.scale(75),

              borderRadius: Responsive.scale(37.5),

            }

          ]}

          source={{ uri: 'https://ejemplo.com/imagen.jpg' }}

        />

      </View>


      {/* Input responsivo */}

      <TextInput

        style={[

          styles.input,

          {

            width: Responsive.widthPercentage(90),

            height: Responsive.verticalScale(40),

            fontSize: Responsive.fontScale(16),

          }

        ]}

        placeholder="Nombre"

      />

    </View>

  );

};


const styles = StyleSheet.create({

  container: {

    flex: 1,

    alignItems: 'center',

    paddingTop: Responsive.verticalScale(40),

  },

  title: {

    fontWeight: '900',

    textTransform: 'uppercase',

    marginBottom: Responsive.verticalScale(30),

  },

  imageContainer: {

    backgroundColor: '#E27683',

    justifyContent: 'center',

    alignItems: 'center',

    marginBottom: Responsive.verticalScale(40),

  },

  image: {

    // Estilos base

  },

  input: {

    borderWidth: 1,

    borderColor: '#ccc',

    borderRadius: Responsive.scale(8),

    paddingHorizontal: Responsive.scale(12),

  },

});


export default Profile;


📊 Paso 8: Comparación de Resultados

Antes (píxeles absolutos):

tsx

// ❌ Se ve diferente en cada dispositivo

<View style={{ width: 100, height: 100 }}>

<Text style={{ fontSize: 20 }}>

Después (dimensiones relativas):

tsx

// ✅ Se escala proporcionalmente

<View style={{ 

  width: getScale(100), 

  height: getScale(100) 

}}>

<Text style={{ fontSize: getScale(20) }}>

Resultados en diferentes dispositivos:

Dispositivo

Ancho Pantalla

getScale(100)

getScale(20)

iPhone SE

320px

84px

17px

iPhone 11

414px

109px

22px

iPhone 12 Pro Max

428px

113px

23px

Variación

+33.8%

+34.5%

+35.3%


💡 Consejos y Mejores Prácticas

1. Escoger un dispositivo base:

  • iPhone 11 (414x896) o iPhone 6/7/8 (375x667)

  • Android común: 360x640

  • Usar el mismo base para todo el proyecto

2. Cuándo usar cada función:

  • getScale(): Para anchos, márgenes horizontales, padding horizontal

  • getVerticalScale(): Para altos, márgenes verticales, padding vertical

  • getModerateScale(): Para fuentes, iconos, elementos que necesiten escalado más suave

3. Testear en múltiples dispositivos:

typescript

// Agregar logs para debug

console.log(`Dispositivo: ${SCREEN_WIDTH}x${SCREEN_HEIGHT}`);

console.log(`Escala 100px: ${getScale(100)}`);

console.log(`Escala 20px: ${getScale(20)}`);

4. Considerar tablets:

typescript

const isTablet = SCREEN_WIDTH >= 768;


if (isTablet) {

  // Layout especial para tablets

  return getScale(size) * 0.8; // Reducir escala en tablets

}


🎓 Resumen

Ventajas de usar dimensiones relativas:

✅ Código más limpio: Elimina múltiples if-else
✅ Mantenible: Cambios en un solo lugar afectan toda la app
✅ Consistente: Misma apariencia en diferentes dispositivos
✅ Eficiente: Cálculos optimizados y reutilizables
✅ Escalable: Fácil agregar soporte para nuevos dispositivos

Flujo recomendado:

  1. Crear archivo de utilidades (scaleUtils.ts)

  2. Definir ancho base de referencia

  3. Usar getScale() en lugar de valores absolutos

  4. Testear en al menos 3 tamaños de pantalla diferentes

  5. Ajustar factor base según necesidades del diseño


📚 Recursos Adicionales

Librerías populares:

Lecturas recomendadas:


🚀 Conclusión

Las dimensiones relativas son esenciales para crear aplicaciones React Native que funcionen bien en la amplia variedad de dispositivos Android e iOS. Al implementar un sistema de escalado como getScale(), aseguras que tu aplicación se vea profesional y consistente, independientemente del dispositivo del usuario.

¡Recuerda: Una vez configurado tu sistema de dimensiones relativas, úsalo consistentemente en todo tu proyecto para obtener los mejores resultados


Comentarios

Entradas más populares de este blog

Guía Paso a Paso para Entender React Native (antes del Tutorial)

Tutorial: Aplicación React Native para Agregar Tareas - Minimalista

5. Vista Rapida: Estructura Projecto Base