Adaptación de Escalado de Navegador en Vue con Resolución Ajustable

Este artículo detalla un método para adaptar la resolución de aplicaciones Vue a diferentes escalas de navegador, especialmente útil en pantallas de alta densidad de píxeles (HiDPI) y al redimensionar la ventana del navegador. Se enfoca en la implementación mediante un plugin personalizado y la configuración de PostCSS.

Método de Adaptación mediante Plugin Personalizado

Se propone una clase DisplayAdapter para gestionar la adaptación dinámica del diseño.

Configuración Inicial

El plugin requiere la instalación de postcss-px2rem.

npm i postcss-px2rem -S

Archivo devicePixelRatio.js

Este archivo contiene la lógica principal para la adaptación.

class DisplayAdapter {
    constructor(options = {}) {
        this.options = {
            designWidth: 1920,       // Ancho de referencia del diseño
            baseFontSize: 16,        // Tamaño de fuente base en px
            maxScale: 2,             // Ratio máximo de escalado
            useTransform: true,      // Usar `transform` para escalar
            debounceTime: 100,       // Tiempo de debounce para eventos
            ...options
        };

        this.lastDPR = window.devicePixelRatio;
        this.init();
    }

    // Detección de sistema (versión mejorada)
    _getSystem () {
        const agent = navigator.userAgent.toLowerCase();
        const isWindows = /windows/.test(agent);
        const isHighDPI = window.devicePixelRatio >= 1.25;
        return isWindows && isHighDPI;
    }

    // Función de debounce
    _debounce (fn) {
        let timer;
        return (...args) => {
            clearTimeout(timer);
            timer = setTimeout(() => fn.apply(this, args), this.options.debounceTime);
        };
    }

    // Cálculo dinámico de REM
    _setRem () {
        const clientWidth = document.documentElement.clientWidth;
        const scale = Math.min(clientWidth / this.options.designWidth, this.options.maxScale);
        document.documentElement.style.fontSize =
            `${this.options.baseFontSize * scale}px`;
    }

    // Corrección de escalado por DPI
    _correctDPR() {
        const currentDPR = window.devicePixelRatio;
        if (Math.abs(currentDPR - this.lastDPR) > 0.05) {
            this.lastDPR = currentDPR;

            if (this.options.useTransform) {
                const scale = 1 / currentDPR;
                document.body.style.transform = `scale(${scale})`;
                document.body.style.transformOrigin = 'top left';
                document.body.style.width = `${this.options.designWidth / scale}px`;
            } else {
                document.body.style.zoom = 1 / currentDPR;
            }
        }
    }

    // Combinación de métodos de adaptación
    _adaptLayout () {
        this._correctDPR();
        this._setRem();
    }

    // Escucha de eventos
    _watch () {
        const debouncedAdapt = this._debounce(() => this._adaptLayout());
        const events = ['resize', 'pageshow', 'orientationchange'];

        events.forEach(event => {
            window.addEventListener(event, debouncedAdapt);
        });

        // Escucha de cambios en DPI (solo navegadores modernos)
        if (window.matchMedia) {
            window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`)
                .addEventListener('change', debouncedAdapt);
        }
    }

    // Inyección de estilos base
    _injectBaseStyles () {
        const style = document.createElement('style');
        style.textContent = `
        html {
          overflow: hidden;
          width: 100vw;
          height: 100vh;
        }
        body {
          position: relative;
          margin: 0;
          padding: 0;
          overflow: hidden;
          transform-origin: top left;
        }
      `;
        document.head.appendChild(style);
    }

    // Inicialización
    init () {
        if (this._getSystem()) {
            this._injectBaseStyles();
            this._adaptLayout();
            this._watch();
        } else {
            // Solo activar adaptación REM en sistemas no Windows
            this._setRem();
            window.addEventListener('resize', this._debounce(() => this._setRem()));
        }
    }
}

// Configuración de inicialización
new DisplayAdapter({
    designWidth: 1920,
    useTransform: true,
    maxScale: 1.5
});

export default DisplayAdapter;

Uso en App.vue

Importar e inicializar la clase en el ciclo de vida created del componente principal.

import DevicePixelRatio from "@/utils/devicePixelRatio"
export default {
  name: "App",
  created () {
    new DevicePixelRatio().init()
  },
};

Configuración de PostCSS

Se deben ajustar los archivos de configuración de Vue CLI y PostCSS.

Archivo vue.config.js

Configurar postcss-px2rem dentro de las opciones de css.loaderOptions.postcss.

// Importar el plugin de adaptación
const px2rem = require('postcss-px2rem');

// Configurar el tamaño base
const postcss = px2rem({
  // Tamaño base `remUnit`, debe coincidir con el definido en `devicePixelRatio.js`
  remUnit: 16,
});

module.exports = {
  outputDir: "dist",
  // publicPath:  '/guess/', // Ejemplo de ruta pública
  publicPath: "/win/", // Ruta pública configurada
  productionSourceMap: false,
  css: {
    loaderOptions: {
      scss: {
        additionalData: `@import "./src/assets/styles/module.scss";`,
        sassOptions: { outputStyle: "expanded" },
      },
      postcss: {
        plugins: [
          postcss,
        ],
      },
    },
  },
};

Archivo postcss.config.js (en la raíz del proyecto)

Configurar autoprefixer y postcss-px2rem con las opciones deseadas.

module.exports = {
    plugins: {
        // Añadir prefijos de proveedor para compatibilidad
        autoprefixer: {
            overrideBrowserslist: [
                "Android 4.1",
                "iOS 7.1",
                "Chrome > 31",
                "ff > 31",
                "ie >= 8",
                "last 10 versions", // Soporte para las 10 últimas versiones de navegadores principales
            ],
            grid: true,
        },
        "postcss-px2rem": {
            rootValue: 16, // Convierte px a rem: ej. 320px -> 20rem si rootValue es 16
            propList: ["*"], // Aplica la conversión a todas las propiedades
            // propList: ["*', '!border*'], // Ejemplo: excluir propiedades de borde
            unitPrecision: 5, // Precisión decimal para rem
            // selectorBlackList: ['.radius'],  // Ignora selectores CSS que coincidan (ej. '.fs')
            replace: true, // Reemplaza las unidades originales con rem
            mediaQuery: false, // Desactiva la conversión en media queries
            minPixelValue: 12, // No convierte valores de px menores a 12
        },
    },
};

// Nota: Si el diseño de referencia es de 375px (ej. para móviles), ajustar `rootValue` a 32.
// Si el diseño es de 750px, `rootValue` debe ser 32.

Este enfoque permite una adaptación consistente del diseño a través de diferentes rseoluciones y niveles de zoom del navegador, asegurando una experiencia de usuario fluida.

Etiquetas: vue.js css responsive design PostCSS px-to-rem

Publicado el 7-27 21:38