CSS3 100vh не постоянный в мобильном браузере

У меня очень странная проблема... в каждом браузере и мобильной версии я сталкивался с таким поведением:

  • у всех браузеров есть верхнее меню при загрузке страницы (например, с адресной строкой), которое сдвигается вверх, когда вы начинаете прокручивать страницу.
  • 100vh рассчитываются только на видимой части области просмотра, поэтому при увеличении полосы браузера 100vh (в пикселях)
  • все макеты перекрасить и заново отрегулировать, так как размеры изменились
  • плохой эффект прыжка для пользовательского опыта

как можно избежать этой проблемы? Когда я впервые услышал о viewport-height, я был взволнован и подумал, что смогу использовать его для блоков фиксированной высоты вместо использования javascript, но теперь я думаю, что единственный способ сделать это - фактически javascript с некоторым событием resize...

Вы можете увидеть проблему по адресу: образец сайта

Кто-нибудь может мне помочь / предложить решение CSS?


простой тестовый код:

/* maybe i can track the issue whe it occours... */
$(function(){
  var resized = -1;
  $(window).resize(function(){
    $('#currenth').val( $('.vhbox').eq(1).height() );
    if (++resized) $('#currenth').css('background:#00c');
  })
  .resize();
})
*{ margin:0; padding:0; }

/*
  this is the box which sould keep constant the height...
  min-height to allow content to be taller than viewport if too much text
*/
.vhbox{
  min-height:100vh;
  position:relative;
}

.vhbox .t{
  display:table;
  position:relative;
  width:100%;
  height:100vh;
}

.vhbox .c{
  height:100%;
  display:table-cell;
  vertical-align:middle;
  text-align:center;
}
<div class="vhbox" style="background-color:#c00">
  <div class="t"><div class="c">
  this div height should be 100% of viewport and keep this height when scrolling page
    <br>
    <!-- this input highlight if resize event is fired -->
    <input type="text" id="currenth">
  </div></div>
</div>

<div class="vhbox" style="background-color:#0c0">
  <div class="t"><div class="c">
  this div height should be 100% of viewport and keep this height when scrolling page
  </div></div>
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

38 ответов

Решение

К сожалению, это намеренно...

Это хорошо известная проблема (по крайней мере, в Safari Mobile), которая является преднамеренной, поскольку предотвращает другие проблемы. Бенджамин Пулен ответил на ошибку веб-набора:

Это совершенно намеренно. С нашей стороны потребовалось немало усилий для достижения этого эффекта.:)

Основная проблема заключается в следующем: видимая область динамически изменяется при прокрутке. Если мы соответствующим образом обновим высоту области просмотра CSS, нам нужно обновить макет во время прокрутки. Мало того, что это похоже на дерьмо, но сделать это при 60 FPS практически невозможно на большинстве страниц (60 FPS - базовая частота кадров на iOS).

Сложно показать вам часть "выглядит как дерьмо", но представьте, что во время прокрутки содержимое перемещается, а то, что вы хотите на экране, постоянно смещается.

Динамическое обновление высоты не работало, у нас было несколько вариантов: отбросить единицы просмотра в iOS, соответствовать размеру документа, как до iOS 8, использовать маленький размер представления, использовать большой размер представления.

Из данных, которые мы имели, использование большего размера представления было лучшим компромиссом. Большинство сайтов, использующих единицы просмотра, выглядели великолепно большую часть времени.

Николя Хойзи исследовал это совсем немного: https://nicolas-hoizey.com/2015/02/viewport-height-is-taller-than-the-visible-part-of-the-document-in-some-mobile-browsers.html

Исправление не запланировано

На данный момент вы ничего не можете сделать, кроме как отказаться от использования высоты области просмотра на мобильных устройствах. Мобильный Chrome, похоже, тоже хочет это адаптировать, хотя не уверен, что они пройдут.

Ты можешь попробовать min-height: -webkit-fill-available; в вашем CSS вместо 100vh, Должно быть решено

У нас есть новые единицы просмотра lvh, svhи на помощь. Это было продемонстрировано в последнем видеоролике Google I/O 2022 о веб-работах.

Вы, вероятно, захотите придерживаться dvhчтобы браузер адаптировался к скрытым вкладкам мобильного устройства при прокрутке. Он работает аналогичным образом для ширины с dvw, lvwа также svwединицы.

Вот красивая иллюстрация из видео: https://youtu.be/Xy9ZXRRgpLk?t=982

Могу ли я использовать?

В настоящее время это работает на моей канарейке Chrome с включенным флагом «Экспериментальные функции».

В моем приложении я делаю это так (машинопись и вложенный postcss, поэтому измените код соответствующим образом):

const appHeight = () => {
    const doc = document.documentElement
    doc.style.setProperty('--app-height', `${window.innerHeight}px`)
}
window.addEventListener('resize', appHeight)
appHeight()

в вашем css:

:root {
   --app-height: 100%;
}

html,
body {
    padding: 0;
    margin: 0;
    overflow: hidden;
    width: 100vw;
    height: 100vh;

    @media not all and (hover:hover) {
        height: var(--app-height);
    }
}

это работает по крайней мере на Chrome Mobile и Ipad. Что не работает, так это когда вы добавляете свое приложение на домашний экран на iOS и меняете ориентацию несколько раз - каким-то образом уровни масштабирования портятся со значением innerHeight, я могу опубликовать обновление, если найду решение для него.

демонстрация

Мне понравился такой трюк:

height: calc(100vh - calc(100vh - 100%))

Посмотрите на этот ответ: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/

// First we get the viewport height and we multiple it by 1% to get a value for a vh unit
let vh = window.innerHeight * 0.01;
// Then we set the value in the --vh custom property to the root of the document
document.documentElement.style.setProperty('--vh', `${vh}px`);

// We listen to the resize event
window.addEventListener('resize', () => {
  // We execute the same script as before
  let vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
});
body {
  background-color: #333;
}

.module {
  height: 100vh; /* Use vh as a fallback for browsers that do not support Custom Properties */
  height: calc(var(--vh, 1vh) * 100);
  margin: 0 auto;
  max-width: 30%;
}

.module__item {
  align-items: center;
  display: flex;
  height: 20%;
  justify-content: center;
}

.module__item:nth-child(odd) {
  background-color: #fff;
  color: #F73859;
}

.module__item:nth-child(even) {
  background-color: #F73859;
  color: #F1D08A;
}
<div class="module">
  <div class="module__item">20%</div>
  <div class="module__item">40%</div>
  <div class="module__item">60%</div>
  <div class="module__item">80%</div>
  <div class="module__item">100%</div>
</div>

установите фиксированное положение тела, установите высоту на 100%

это оно

тогда мобильный браузер поймет, что вы хотите

Вы можете сделать это, добавив следующий скрипт и стиль

  function appHeight() {
    const doc = document.documentElement
    doc.style.setProperty('--vh', (window.innerHeight*.01) + 'px');
  }

  window.addEventListener('resize', appHeight);
  appHeight();

Стиль

.module {
  height: 100vh; /* Fallback for browsers that do not support Custom Properties */
  height: calc(var(--vh, 1vh) * 100);
}

Для многих сайтов, которые я создаю, клиент будет запрашивать 100-вольтовый баннер, и, как вы уже нашли, это приводит к плохому "нервному" впечатлению на мобильном телефоне, когда вы начинаете прокручивать. Вот как я решаю проблему для бесперебойного согласованного взаимодействия на всех устройствах:

Сначала я установил свой элемент баннера CSS на height:100vh

Затем я использую jQuery для получения высоты в пикселях моего элемента баннера и применяю встроенный стиль, используя эту высоту.

var viewportHeight = $('.banner').outerHeight();
$('.banner').css({ height: viewportHeight });

Это решает проблему на мобильных устройствах, так как при загрузке страницы элемент баннера устанавливается на 100vh с использованием CSS, а затем jQuery переопределяет это, помещая встроенный CSS в мой элемент баннера, что останавливает его изменение размера, когда пользователь начинает прокручивать.

Однако на рабочем столе, если пользователь изменяет размер своего окна браузера, размер элемента баннера не изменится, потому что теперь он имеет фиксированную высоту, заданную в пикселях из-за вышеупомянутого jQuery. Для решения этой проблемы я использую Mobile Detect, чтобы добавить "мобильный" класс в тело моего документа. А затем я обертываю вышеупомянутый jQuery в оператор if:

if ($('body').hasClass('mobile')) {
  var viewportHeight = $('.banner').outerHeight();
  $('.banner').css({ height: viewportHeight });
}

В результате, если пользователь находится на мобильном устройстве, класс "mobile" присутствует в теле моей страницы, и вышеупомянутый jQuery выполняется. Таким образом, мой баннерный элемент будет применять только встроенный CSS на мобильных устройствах, в то время как на настольном компьютере оригинальное правило CSS 100vh остается в силе.

Я придумал компонент React - проверьте его, если вы используете React, или просмотрите исходный код, если вы этого не сделаете, чтобы вы могли адаптировать его к вашей среде.

Устанавливает высоту полноэкранного div в window.innerHeight а затем обновляет его при изменении размеров окна.

Поскольку несколько дней я искал решение, вот мое для всех, кто использует VueJS с Vuetify (в моем решении используются v-app-bar, v-navigation-drawer и v-footer): я создал App.scss (используется в App.vue) со следующим содержанием:

.v-application {
    height: 100vh;
    height: -webkit-fill-available;
}

.v-application--wrap {
    min-height: 100vh !important;
    min-height: -webkit-fill-available !important;
}

Вы можете попробовать дать position: fixed; top: 0; bottom: 0; свойства вашего контейнера.

Проблема до сих пор остается, к сожалению. И самое большое заблуждение, что невозможно представить ситуацию с помощью браузера.devices toolbar.

Я только что решил проблему так (проверено в браузерах ПК, iOS и Android):

      .your_class {
   height: 100vh,
   max-height: 100%, // <-- add the line
   ...some other props,
}

Надеюсь, это сэкономит ваше время.

@nils объяснил это ясно.

Что дальше?

Я просто вернулся, чтобы использовать относительный "классический" %(процент) в CSS.

Часто требуется больше усилий для реализации чего-либо, чем было бы vhНо, по крайней мере, у вас есть довольно стабильное решение, которое работает на разных устройствах и в разных браузерах без странных глюков интерфейса.

Это тот трюк, который вам понадобится https://css-tricks.com/the-trick-to-viewport-units-on-mobile/.

CSS

.my-element {
    height: 100vh;                      /* Fallback for browsers that do not support Custom Properties */
    height: calc(var(--vh, 1vh) * 100);
    transition: 1s;                     /* To avoid a jumpy result */
}

JS

// First we get the viewport height and we multiple it by 1% to get a value for a vh unit
let vh = window.innerHeight * 0.01;

// Then we set the value in the --vh custom property to the root of the document
document.documentElement.style.setProperty('--vh', `${vh}px`);

// We listen to the resize event
window.addEventListener('resize', () => {

    // We execute the same script as before
    let vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty('--vh', `${vh}px`);
});

Следующий код решил проблему (с помощью jQuery).

var vhHeight = $("body").height();
var chromeNavbarHeight = vhHeight - window.innerHeight;
$('body').css({ height: window.innerHeight, marginTop: chromeNavbarHeight });

И другие элементы используют % как блок для замены vh,

Я создал два примера ниже:

  1. Чтобы продемонстрировать, как height: 100vhпоскольку высота может привести к прокрутке в мобильных браузерах Chrome:

код : https://codesandbox.io/embed/mobile-viewport-100vh-issue-nxx8z?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark

демо : https://nxx8z.csb.app/


  1. Решение с использованием position: fixedдля решения проблемы и с чисто CSS:

код : https://codesandbox.io/s/mobile-viewport-100vh-issue-fix-forked-ypx5x?file=/index.html

демо : https://ypx5x.csb.app/

Я только что обнаружил, что разработанное мною веб-приложение имеет эту проблему с iPhone и iPad, и нашел статью, предлагающую решить его с помощью медиазапросов, предназначенных для определенных устройств Apple.

Я не знаю, могу ли я поделиться кодом из этой статьи здесь, но адрес таков: http://webdesignerwall.com/tutorials/css-fix-for-ios-vh-unit-bug

Выход из статьи: "просто сопоставьте высоту элемента с высотой устройства, используя медиа-запросы, которые нацелены на более старые версии iPhone и iPad".

Они добавили всего 6 медиа-запросов, чтобы адаптировать элементы полной высоты, и это должно работать, поскольку это полностью реализовано CSS.

Ожидание редактирования: я не могу проверить это прямо сейчас, но я вернусь и сообщу о своих результатах.

Вот способ, который я использовал для своего приложения React.

iPhone 11 Pro и iPhone Pro Max - 120 пикселей

iPhone 8 - 80 пикселей

max-height: calc(100vh - 120px);

Это компромисс, но относительно простое решение

Не используйте рекомендуемые подходы, такие как.
Я просто целый день носился, чтобы исправить эту «ошибку».

Добавьте класс, когда ваше приложение загружается с помощью браузера с «подбородком».

JavaScript

      // Angular example but applicable for any JS solution
@HostBinding('class.browser-has-chin') browserHasChin: boolean = false;

public ngOnInit(): void {
    this.browserHasChin = this._isMobileSafari();
}

private _isMobileSafari() {
    return navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/) ? true : false;
}

CSS

      .browser-has-chin {
  @media screen and (max-device-width: 767px){
    // offset with padding or something
  }
}

ПРИМЕЧАНИЯ:
Есть серьезные проблемы с опорой для кроссбраузерной совместимости.

Мне удалось заставить его работать в Chrome и iOS Safari, чтобы исправить проблему с вычислением подбородка / высоты. Однако он сломал Android Chrome, и в Firefox тоже были ошибки.

Кажется, что -webkit-fill-available в какой-то момент был загружен в webkit и, возможно, случайно принят Apple в качестве исправления для расчета подбородка / роста?

Он полагается на внутренние размеры, которые пока НЕ ​​безопасны в использовании.

Решение React Hooks с использованием useEffect и useState

          function App() {
      const [vh, setVh] = useState(window.innerHeight);
    
      useEffect(() => {
        const updateVh = () => {
          setVh(window.innerHeight);
        };
    
        window.addEventListener('resize', updateVh);
    
        return () => window.removeEventListener('resize', updateVh);
      }, []);
    
      return (
        <div style={{ height: vh }}>
          {vh} px
        </div>
      );
}

Демо: https://jsfiddle.net/poooow/k570nfd9/

Поскольку я новичок, я не могу комментировать другие ответы.

Если кто-то ищет ответ, чтобы сделать эту работу (и может использовать javascript - поскольку, кажется, требуется сделать эту работу в данный момент), этот подход сработал довольно хорошо для меня, и он также учитывает изменение ориентации мобильного устройства. Я использую Jquery для примера кода, но должен быть выполним с vanillaJS.

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

if ("ontouchstart" in document.documentElement) {
    document.body.classList.add('touch-device');

} else {
    document.body.classList.add('hover-device');
}

Это добавляет класс к элементу body в соответствии с типом устройства (указатель мыши или касание), который можно использовать позже для сценария высоты.

-Далее используйте этот код для установки высоты устройства при нагрузке и изменении ориентации:

if (jQuery('body').hasClass("touch-device")) {
//Loading height on touch-device
    function calcFullHeight() {
        jQuery('.hero-section').css("height", $(window).height());
    }

    (function($) {
        calcFullHeight();

        jQuery(window).on('orientationchange', function() {
            // 500ms timeout for getting the correct height after orientation change
            setTimeout(function() {
                calcFullHeight();
            }, 500);

        });
    })(jQuery);

} else {
    jQuery('.hero-section').css("height", "100vh");


}

-Timeout устанавливается так, чтобы устройство правильно рассчитывало новую высоту при изменении ориентации. Если тайм-аута нет, по моему опыту, высота не будет правильной. 500 мс может быть переусердствовать, но у меня сработало.

-100vh на hover-устройствах - это запасной вариант, если браузер переопределяет CSS 100vh.

Следующее работало для меня:

html { height: 100vh; }

body {
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100vw;
}

/* this is the container you want to take the visible viewport  */
/* make sure this is top-level in body */
#your-app-container {
  height: 100%;
}

body примет видимую высоту области просмотра и #your-app-container с height: 100% заставит этот контейнер принять видимую высоту области просмотра.

VH 100 плохо работает на мобильных устройствах, поскольку он не учитывает панель iOS (или аналогичные функции на других платформах).

Одно из эффективных решений - использовать JavaScript "window.innerHeight".

Просто присвойте этому значению высоту элемента, например, $('. Element-name'). Height(window.innerHeight);

Примечание: может быть полезно создать функцию в JS, чтобы высота могла изменяться при изменении размера экрана. Однако я бы предложил вызывать функцию только при изменении ширины экрана, таким образом, элемент не будет прыгать по высоте, когда панель iOS исчезает, когда пользователь прокручивает страницу вниз.

      body {
   width: 100%;
   height: 100%
}

100% автоматически подстраивается под размер экрана, но по какой-то причине у 100vh возникают проблемы с настройкой при изменении экрана, поэтому использование нижнего 0 будет затронуто, поэтому, если вы используете 100vh или 100%

например, когда панель поиска находится в поле зрения или не в мобильном телефоне, 100% с изменением, где 100vh будет привязано к первому значению высоты экрана.

Я решил это, поместив самый внешний div в position: absolute а затем просто установите height к 100%:

CSS:

      .outer {
    position: absolute;
    height: 100%;
}

HTML:

      <div class="outer">
    <!-- content -->
</div>

Использование vh на мобильных устройствах не будет работать с 100vh, из-за их выбора дизайна, используя всю высоту устройства, не включая адресные строки и т. Д.

Если вы ищете макет, включающий высоты div, пропорциональные истинной высоте просмотра, я использую следующее чистое решение CSS:

:root {
  --devHeight: 86vh; //*This value changes
}

.div{
    height: calc(var(--devHeight)*0.10); //change multiplier to suit required height
}

У вас есть два варианта установки высоты области просмотра, вручную установите --devHeight на высоту, которая работает (но вам нужно будет ввести это значение для каждого типа устройства, для которого вы кодируете)

или же

Используйте javascript, чтобы получить высоту окна, а затем обновите --devheight при загрузке и обновлении области просмотра (однако это требует использования javascript и не является чистым решением css)

Как только вы получите правильную высоту просмотра, вы можете создать несколько элементов div с точным процентом от общей высоты области просмотра, просто изменив множитель в каждом элементе div, которому вы назначаете высоту.

0,10 = 10% высоты обзора 0,57 = 57% высоты обзора

Надеюсь, это может кому-то помочь;)

Поскольку это не будет исправлено, вы можете сделать что-то вроде:

# html
<body>
  <div class="content">
    <!-- Your stuff here -->
  </div>
</body>

# css
.content {
  height: 80vh;
}

Для меня это было самое быстрое и более чистое решение, чем игра с JavaScript, который не мог работать на многих устройствах и браузерах.

Просто используйте правильное значение vh который соответствует вашим потребностям.

Надеемся, что это будет переменная среды CSS, определенная UA, как предлагается здесь: https://github.com/w3c/csswg-drafts/issues/2630

Хорошее чтение о проблеме и ее возможных решениях можно найти в этом сообщении в блоге: Обращение к адресной строке iOS в макетах 100vh

Решение, которое я нашел в своем приложении React, использует библиотеку react-div-100vh, описанную в сообщении выше.

Другие вопросы по тегам