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

17. Estructura de una aplicación de React Native, una vista mas profunda

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

12. Configuracion del entorno de desarrollo en Windows