Skip to content

实现 PX↔REM/VW 换算 工具

分类:general | 标签:px、rem、vw 按根字号批量转换 CSS 中的 px/rem/vw

2.1 功能说明

按根字号批量转换 CSS 中的 px/rem/vw

2.1.1 使用指南

功能说明

在移动端和响应式适配中,把 CSS 里的固定像素批量换算成相对单位(rem 或 vw),或者反向还原成像素,便于核对设计稿。整段 CSS 粘进来即可,注释、选择器和其它属性都会原样保留,只替换带单位的数值。

两个基准参数

  • 根字号:html 元素的 font-size,rem 换算的基准。默认 16 是浏览器默认值;如果项目用了 flexible 方案把根字号设成 37.5 或 75,请改成对应数值。
  • 基准视口宽度:设计稿宽度,vw 换算的基准。默认 375 对应主流移动端设计稿,750 的二倍图设计稿请填 750。

换算公式

  • px 转 rem:数值 除以 根字号
  • rem 转 px:数值 乘以 根字号
  • px 转 vw:数值 除以 视口宽度 再乘 100
  • vw 转 px:数值 除以 100 再乘 视口宽度

选项说明

  • 保留小数位:结果四舍五入到指定位数,并自动去掉末尾多余的 0,例如 1.500 会输出 1.5。
  • 保留 1px 不转换:常用于 1px 边框和分割线,转成相对单位后在部分设备上会被抹掉或变粗,建议保持勾选。
  • 保留 0 值不转换:0px 转换后没有意义,勾选后保持原样。

匹配规则与注意事项

使用正则匹配数值加单位的组合,支持负号和小数(例如 -0.5px.75rem),单位大小写不敏感。需要注意:工具不解析 CSS 语法树,因此字符串内容、注释里的数值、以及 url() 中恰好形如数值加单位的文本也会被一并替换。转换后建议快速浏览一遍结果,或先在小片段上试转。

2.2 代码实现

2.2.1 组件结构

本工具是一个基于 Vue 3 <script setup> 语法的单文件组件(SFC),统一包裹在 ToolPageShell 组件内,由它提供页面标题、工具 ID、分类与「使用指南」插槽等通用外壳;核心业务逻辑(响应式状态、计算属性、事件处理函数)全部写在 <script setup> 中,输入/输出通过 el-inputel-button 等 Element Plus 组件与用户交互,所有数据均在浏览器本地处理,不会上传服务器。

2.2.2 核心逻辑概览

<script setup> 中定义的主要函数/方法:

  • convertValue()
  • formatValue()
  • loadSample()
  • applyToInput()
  • clearAll()
  • copyOutput()
  • downloadOutput()

2.2.3 关键实现代码

以下是该工具的完整 <script setup> 实现(核心代码)。模板(<template>)与样式(<style>)已省略。

vue
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import ToolPageShell from '@/components/ToolPageShell.vue'

const rootFontSize = ref(16)
const viewportWidth = ref(375)
const precision = ref(6)
const keepOnePx = ref(true)
const keepZero = ref(true)
const mode = ref('px2rem')
const cssInput = ref('')
const singleValue = ref(16)

const formulas = {
  px2rem: () => `当前公式:rem 值 = px 值 / ${rootFontSize.value}`,
  rem2px: () => `当前公式:px 值 = rem 值 × ${rootFontSize.value}`,
  px2vw: () => `当前公式:vw 值 = px 值 / ${viewportWidth.value} × 100`,
  vw2px: () => `当前公式:px 值 = vw 值 / 100 × ${viewportWidth.value}`
}
const formulaText = computed(() => formulas[mode.value]())

const sourceUnit = computed(() => mode.value.slice(0, mode.value.indexOf('2')))
const targetUnit = computed(() => mode.value.slice(mode.value.indexOf('2') + 1))
const singleUnitLabel = computed(() => sourceUnit.value)

function convertValue(n) {
  if (mode.value === 'px2rem') return n / rootFontSize.value
  if (mode.value === 'rem2px') return n * rootFontSize.value
  if (mode.value === 'px2vw') return (n / viewportWidth.value) * 100
  return (n / 100) * viewportWidth.value
}

function formatValue(n) {
  if (!Number.isFinite(n)) return '0'
  const fixed = n.toFixed(precision.value)
  // 去掉末尾多余的零,例如 1.500000 输出 1.5
  return fixed.indexOf('.') === -1 ? fixed : fixed.replace(/0+$/, '').replace(/\.$/, '')
}

const conversion = computed(() => {
  const source = cssInput.value
  if (!source) return { text: '', count: 0 }
  const unit = sourceUnit.value
  const pattern = new RegExp('(-?(?:\\d+\\.?\\d*|\\.\\d+))' + unit + '\\b', 'gi')
  let count = 0
  const text = source.replace(pattern, (match, numText) => {
    const n = parseFloat(numText)
    if (!Number.isFinite(n)) return match
    if (keepZero.value && n === 0) return match
    if (keepOnePx.value && unit === 'px' && Math.abs(n) === 1) return match
    count++
    return formatValue(convertValue(n)) + targetUnit.value
  })
  return { text, count }
})

const cssOutput = computed(() => conversion.value.text)
const convertedCount = computed(() => conversion.value.count)

const quickResult = computed(() => {
  const n = Number(singleValue.value) || 0
  const converted = formatValue(convertValue(n))
  return {
    labelA: `${sourceUnit.value} 值`,
    valueA: `${n}${sourceUnit.value}`,
    labelB: `${targetUnit.value} 值`,
    valueB: `${converted}${targetUnit.value}`
  }
})

function loadSample() {
  cssInput.value = [
    '.card {',
    '  width: 350px;',
    '  padding: 12px 16px;',
    '  margin: -8px auto 24px;',
    '  border: 1px solid #e4e7ed;',
    '  border-radius: 8px;',
    '  font-size: 14px;',
    '  line-height: 22px;',
    '  letter-spacing: 0.5px;',
    '}',
    '',
    '.card__title {',
    '  font-size: 18px;',
    '  margin-bottom: 0px;',
    '}'
  ].join('\n')
}

function applyToInput() {
  cssInput.value = cssOutput.value
  ElMessage.success('已回填,可继续做下一步换算')
}

function clearAll() {
  cssInput.value = ''
}

async function copyOutput() {
  if (!cssOutput.value) return
  try {
    await navigator.clipboard.writeText(cssOutput.value)
    ElMessage.success('已复制到剪贴板')
  } catch {
    ElMessage.warning('复制失败,请手动选择文本复制')
  }
}

function downloadOutput() {
  if (!cssOutput.value) return
  const blob = new Blob([cssOutput.value], { type: 'text/css;charset=utf-8' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = `converted-${mode.value}.css`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
  ElMessage.success('已开始下载')
}

2.3 效果截图

PX↔REM/VW 换算 效果截图

工具访问地址:https://www.i91tools.com/tools/px-rem