← HTML & CSS/Логические свойства: интернационализация CSS#41 из 383← ПредыдущийСледующий →+15 XP
Полезно по теме:Гайд: старт в frontendПрактика: DOM и событияТермин: DOMМаршрут: старт с нуля

Логические свойства: интернационализация CSS

Арабский текст читается справа налево. Японский — сверху вниз. Если написать margin-left: 16px, на RTL-странице этот отступ будет не с «начала» текста, а с «конца». Логические свойства решают это: они описывают расположение относительно направления текста, а не физических сторон.

Физические vs Логические свойства

| Физическое | Логическое | Значение |

|---------------------|-----------------------------|-----------------------------|

| margin-left | margin-inline-start | Начало строки |

| margin-right | margin-inline-end | Конец строки |

| margin-top | margin-block-start | Начало блока (верх) |

| margin-bottom | margin-block-end | Конец блока (низ) |

| padding-left | padding-inline-start | — |

| width | inline-size | Размер в направлении строки |

| height | block-size | Размер в направлении блока |

| top | inset-block-start | — |

| left | inset-inline-start | — |

Inline и Block оси

  • Inline — направление текста (LTR: слева направо, RTL: справа налево)
  • Block — перпендикулярно тексту (для горизонтального письма: сверху вниз)
  • /* LTR (по умолчанию): inline-start = left, block-start = top */
    /* RTL: inline-start = right, block-start = top */
    /* Вертикальный японский: inline-start = top, block-start = right */

    Основные логические свойства

    .article {
      /* Вместо margin-left и margin-right */
      margin-inline: auto;          /* margin-inline-start + margin-inline-end */
      margin-inline-start: 24px;   /* margin-left в LTR, margin-right в RTL */
    
      /* Вместо padding-top и padding-bottom */
      padding-block: 32px;          /* padding-block-start + padding-block-end */
      padding-inline: 16px;
    
      /* Вместо width */
      inline-size: 600px;           /* Ширина в LTR, высота при вертикальном тексте */
      max-inline-size: 100%;
      min-block-size: 400px;        /* min-height в LTR */
    }
    
    /* Позиционирование */
    .tooltip {
      inset-inline-start: 0;        /* left: 0 в LTR, right: 0 в RTL */
      inset-block-start: 100%;      /* top: 100% */
      inset: 0;                     /* Все четыре стороны: top right bottom left */
    }
    
    /* Границы */
    .card {
      border-inline-start: 4px solid #7b2ff7;  /* border-left в LTR */
      border-block-end: 1px solid #e2e8f0;     /* border-bottom */
      border-start-start-radius: 8px;          /* border-top-left-radius в LTR */
    }

    writing-mode и direction

    /* Направление текста */
    html[dir="rtl"] { direction: rtl; }  /* Арабский, иврит */
    html[lang="ar"] { direction: rtl; }
    
    /* Режим письма */
    .vertical-text {
      writing-mode: vertical-rl;   /* Вертикальный, справа налево (японский) */
      writing-mode: vertical-lr;   /* Вертикальный, слева направо */
    }

    Почему это важно

    /* Плохо — сломается в RTL */
    .breadcrumb-item + .breadcrumb-item::before {
      margin-left: 8px;
      margin-right: 8px;
    }
    
    /* Хорошо — работает в любом направлении */
    .breadcrumb-item + .breadcrumb-item::before {
      margin-inline: 8px;
    }
    
    /* Плохо — иконка всегда слева */
    .button-with-icon {
      padding-left: 40px;
    }
    
    /* Хорошо — иконка с «начала» текста */
    .button-with-icon {
      padding-inline-start: 40px;
    }

    Поддержка браузеров

    Все современные браузеры поддерживают логические свойства. Для старых браузеров используй fallback:

    .element {
      margin-left: 16px;          /* Fallback */
      margin-inline-start: 16px;  /* Логическое (переопределит если поддерживается) */
    }

    Примеры

    Демонстрация разницы физических и логических свойств в LTR и RTL окружении

    // Демонстрация логических vs физических свойств в LTR и RTL
    function createDemo(direction) {
      const container = document.createElement('div')
      container.dir = direction
      container.style.cssText = `
        border: 2px solid ${direction === 'ltr' ? '#7b2ff7' : '#f59e0b'};
        padding: 16px;
        border-radius: 8px;
        font-family: sans-serif;
        margin-bottom: 16px;
      `
    
      const label = document.createElement('div')
      label.textContent = `direction: ${direction.toUpperCase()}`
      label.style.cssText = `
        font-weight: 700;
        margin-bottom: 12px;
        color: ${direction === 'ltr' ? '#7b2ff7' : '#d97706'};
      `
      container.appendChild(label)
    
      // Элемент с ФИЗИЧЕСКИМ свойством
      const physical = document.createElement('div')
      physical.textContent = direction === 'ltr' ? '→ margin-left: 32px (физическое)' : '← margin-left: 32px (физическое — не адаптируется!)'
      physical.style.cssText = `
        margin-left: 32px;
        background: #fef3c7;
        padding: 8px;
        border-radius: 4px;
        margin-bottom: 8px;
        font-size: 14px;
      `
      container.appendChild(physical)
    
      // Элемент с ЛОГИЧЕСКИМ свойством
      const logical = document.createElement('div')
      logical.textContent = direction === 'ltr'
        ? '→ margin-inline-start: 32px (логическое — LTR = left)'
        : '→ margin-inline-start: 32px (логическое — RTL = right!)'
      logical.style.cssText = `
        margin-inline-start: 32px;
        background: #d1fae5;
        padding: 8px;
        border-radius: 4px;
        font-size: 14px;
      `
      container.appendChild(logical)
    
      return container
    }
    
    document.body.appendChild(createDemo('ltr'))
    document.body.appendChild(createDemo('rtl'))
    
    // Маппинг логических → физические
    function getPhysicalProperty(logicalProp, direction = 'ltr', writingMode = 'horizontal-tb') {
      const map = {
        ltr: {
          'margin-inline-start':   'margin-left',
          'margin-inline-end':     'margin-right',
          'margin-block-start':    'margin-top',
          'margin-block-end':      'margin-bottom',
          'padding-inline-start':  'padding-left',
          'padding-inline-end':    'padding-right',
          'inset-inline-start':    'left',
          'inset-inline-end':      'right',
          'inline-size':           'width',
          'block-size':            'height',
          'border-inline-start':   'border-left',
        },
        rtl: {
          'margin-inline-start':   'margin-right',
          'margin-inline-end':     'margin-left',
          'padding-inline-start':  'padding-right',
          'padding-inline-end':    'padding-left',
          'inset-inline-start':    'right',
          'inset-inline-end':      'left',
          'border-inline-start':   'border-right',
        },
      }
      const dirMap = direction === 'rtl' ? { ...map.ltr, ...map.rtl } : map.ltr
      return dirMap[logicalProp] || logicalProp
    }
    
    console.log('=== LTR маппинг ===')
    ;['margin-inline-start', 'padding-inline-end', 'inline-size', 'inset-inline-start'].forEach(prop => {
      console.log(`${prop} → ${getPhysicalProperty(prop, 'ltr')}`)
    })
    
    console.log('\n=== RTL маппинг ===')
    ;['margin-inline-start', 'padding-inline-end', 'inset-inline-start'].forEach(prop => {
      console.log(`${prop} → ${getPhysicalProperty(prop, 'rtl')}`)
    })

    Логические свойства: интернационализация CSS

    Арабский текст читается справа налево. Японский — сверху вниз. Если написать margin-left: 16px, на RTL-странице этот отступ будет не с «начала» текста, а с «конца». Логические свойства решают это: они описывают расположение относительно направления текста, а не физических сторон.

    Физические vs Логические свойства

    | Физическое | Логическое | Значение |

    |---------------------|-----------------------------|-----------------------------|

    | margin-left | margin-inline-start | Начало строки |

    | margin-right | margin-inline-end | Конец строки |

    | margin-top | margin-block-start | Начало блока (верх) |

    | margin-bottom | margin-block-end | Конец блока (низ) |

    | padding-left | padding-inline-start | — |

    | width | inline-size | Размер в направлении строки |

    | height | block-size | Размер в направлении блока |

    | top | inset-block-start | — |

    | left | inset-inline-start | — |

    Inline и Block оси

  • Inline — направление текста (LTR: слева направо, RTL: справа налево)
  • Block — перпендикулярно тексту (для горизонтального письма: сверху вниз)
  • /* LTR (по умолчанию): inline-start = left, block-start = top */
    /* RTL: inline-start = right, block-start = top */
    /* Вертикальный японский: inline-start = top, block-start = right */

    Основные логические свойства

    .article {
      /* Вместо margin-left и margin-right */
      margin-inline: auto;          /* margin-inline-start + margin-inline-end */
      margin-inline-start: 24px;   /* margin-left в LTR, margin-right в RTL */
    
      /* Вместо padding-top и padding-bottom */
      padding-block: 32px;          /* padding-block-start + padding-block-end */
      padding-inline: 16px;
    
      /* Вместо width */
      inline-size: 600px;           /* Ширина в LTR, высота при вертикальном тексте */
      max-inline-size: 100%;
      min-block-size: 400px;        /* min-height в LTR */
    }
    
    /* Позиционирование */
    .tooltip {
      inset-inline-start: 0;        /* left: 0 в LTR, right: 0 в RTL */
      inset-block-start: 100%;      /* top: 100% */
      inset: 0;                     /* Все четыре стороны: top right bottom left */
    }
    
    /* Границы */
    .card {
      border-inline-start: 4px solid #7b2ff7;  /* border-left в LTR */
      border-block-end: 1px solid #e2e8f0;     /* border-bottom */
      border-start-start-radius: 8px;          /* border-top-left-radius в LTR */
    }

    writing-mode и direction

    /* Направление текста */
    html[dir="rtl"] { direction: rtl; }  /* Арабский, иврит */
    html[lang="ar"] { direction: rtl; }
    
    /* Режим письма */
    .vertical-text {
      writing-mode: vertical-rl;   /* Вертикальный, справа налево (японский) */
      writing-mode: vertical-lr;   /* Вертикальный, слева направо */
    }

    Почему это важно

    /* Плохо — сломается в RTL */
    .breadcrumb-item + .breadcrumb-item::before {
      margin-left: 8px;
      margin-right: 8px;
    }
    
    /* Хорошо — работает в любом направлении */
    .breadcrumb-item + .breadcrumb-item::before {
      margin-inline: 8px;
    }
    
    /* Плохо — иконка всегда слева */
    .button-with-icon {
      padding-left: 40px;
    }
    
    /* Хорошо — иконка с «начала» текста */
    .button-with-icon {
      padding-inline-start: 40px;
    }

    Поддержка браузеров

    Все современные браузеры поддерживают логические свойства. Для старых браузеров используй fallback:

    .element {
      margin-left: 16px;          /* Fallback */
      margin-inline-start: 16px;  /* Логическое (переопределит если поддерживается) */
    }

    Примеры

    Демонстрация разницы физических и логических свойств в LTR и RTL окружении

    // Демонстрация логических vs физических свойств в LTR и RTL
    function createDemo(direction) {
      const container = document.createElement('div')
      container.dir = direction
      container.style.cssText = `
        border: 2px solid ${direction === 'ltr' ? '#7b2ff7' : '#f59e0b'};
        padding: 16px;
        border-radius: 8px;
        font-family: sans-serif;
        margin-bottom: 16px;
      `
    
      const label = document.createElement('div')
      label.textContent = `direction: ${direction.toUpperCase()}`
      label.style.cssText = `
        font-weight: 700;
        margin-bottom: 12px;
        color: ${direction === 'ltr' ? '#7b2ff7' : '#d97706'};
      `
      container.appendChild(label)
    
      // Элемент с ФИЗИЧЕСКИМ свойством
      const physical = document.createElement('div')
      physical.textContent = direction === 'ltr' ? '→ margin-left: 32px (физическое)' : '← margin-left: 32px (физическое — не адаптируется!)'
      physical.style.cssText = `
        margin-left: 32px;
        background: #fef3c7;
        padding: 8px;
        border-radius: 4px;
        margin-bottom: 8px;
        font-size: 14px;
      `
      container.appendChild(physical)
    
      // Элемент с ЛОГИЧЕСКИМ свойством
      const logical = document.createElement('div')
      logical.textContent = direction === 'ltr'
        ? '→ margin-inline-start: 32px (логическое — LTR = left)'
        : '→ margin-inline-start: 32px (логическое — RTL = right!)'
      logical.style.cssText = `
        margin-inline-start: 32px;
        background: #d1fae5;
        padding: 8px;
        border-radius: 4px;
        font-size: 14px;
      `
      container.appendChild(logical)
    
      return container
    }
    
    document.body.appendChild(createDemo('ltr'))
    document.body.appendChild(createDemo('rtl'))
    
    // Маппинг логических → физические
    function getPhysicalProperty(logicalProp, direction = 'ltr', writingMode = 'horizontal-tb') {
      const map = {
        ltr: {
          'margin-inline-start':   'margin-left',
          'margin-inline-end':     'margin-right',
          'margin-block-start':    'margin-top',
          'margin-block-end':      'margin-bottom',
          'padding-inline-start':  'padding-left',
          'padding-inline-end':    'padding-right',
          'inset-inline-start':    'left',
          'inset-inline-end':      'right',
          'inline-size':           'width',
          'block-size':            'height',
          'border-inline-start':   'border-left',
        },
        rtl: {
          'margin-inline-start':   'margin-right',
          'margin-inline-end':     'margin-left',
          'padding-inline-start':  'padding-right',
          'padding-inline-end':    'padding-left',
          'inset-inline-start':    'right',
          'inset-inline-end':      'left',
          'border-inline-start':   'border-right',
        },
      }
      const dirMap = direction === 'rtl' ? { ...map.ltr, ...map.rtl } : map.ltr
      return dirMap[logicalProp] || logicalProp
    }
    
    console.log('=== LTR маппинг ===')
    ;['margin-inline-start', 'padding-inline-end', 'inline-size', 'inset-inline-start'].forEach(prop => {
      console.log(`${prop} → ${getPhysicalProperty(prop, 'ltr')}`)
    })
    
    console.log('\n=== RTL маппинг ===')
    ;['margin-inline-start', 'padding-inline-end', 'inset-inline-start'].forEach(prop => {
      console.log(`${prop} → ${getPhysicalProperty(prop, 'rtl')}`)
    })

    Задание

    Создай компонент навигационной хлебной крошки, который корректно работает в LTR и RTL режимах. Используй логические свойства: `margin-inline-start`, `padding-inline`, `border-inline-start`. Переключай направление текста через `dir` атрибут на `<html>` и кнопку.

    Подсказка

    `margin-inline: 6px` — горизонтальные отступы (заменяет `margin-left` + `margin-right`). `border-inline-start: 4px solid #7b2ff7` — граница с "начала" строки (слева в LTR, справа в RTL). `padding-inline-start: 16px`. `padding-block: 12px`. Нажми кнопку и посмотри как граница переходит на другую сторону.

    Загружаем среду выполнения...
    Загружаем AI-помощника...