Android Chrome CanvasRenderingContext2D неверный масштаб холста

#android #google-chrome #html5-canvas

Вопрос:

Я пытаюсь создать игру, которая работает на холсте HTML 5, чтобы она выглядела одинаково на всех устройствах, и я нахожусь там большую часть пути. Игра обычно изменяет размер, чтобы заполнить либо 100% доступной высоты, либо 100% доступной ширины (в зависимости от того, что лучше), сохраняя соотношение сторон 9:16. Однако некоторые мобильные устройства Android, просматривающие страницу в Chrome, неправильно масштабируют содержимое холста. Часть содержимого холста не отображается на экране, что делает игру неиграбельной.

MindmetriQ отображается правильно

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

Тот факт, что браузер не показывает полосы прокрутки, означает, что сам холст правильно заполняет доступное пространство, это должно быть масштабирование CanvasRenderingContext2D, которое работает неправильно. Только Android Chrome демонстрирует такое поведение, и только для определенных устройств. Работая со списком комбинаций устройств/браузеров в BrowserStack (не путать с BrowserSack), я обнаружил, что он неправильно отображается в этих комбинациях:

  • OnePlus 8 — Хром
  • Motorola Moto G7 Play — Chrome
  • Xiaomi Redmi Note 9 — Хром
  • Oppo Reno 3 Pro — Хром

Я не был исчерпывающим при тестировании различных комбинаций устройств и браузеров, но я попробовал все доступные браузеры на устройствах всех доступных производителей. Кроме того, мои коллеги (и некоторые из наших реальных пользователей), использующие реальные устройства, обнаружили дополнительные комбинации, которые имели такое же поведение, в основном с Android Chrome. Мы никогда не воспроизводили эту проблему ни на настольных компьютерах, ни на планшетах, и редко на телефонах iOS.

Игровой набор называется MindmetriQ и развернут здесь для производства. (В наборе MindmetriQ есть еще 5 игр, которые в равной степени затронуты, но эта игра лучше всего демонстрирует проблему.) Сама игра написана на машинке, и мы встроили ее в ASP .Страница Net MVC.

Машинописный текст (событие-прослушиватель.ts):

 private scaleCanvasToAvailableSpace(maxAvailableWidth: number, maxAvailableHeight: number): void {
    //Anything in all caps is a number constant defined in constants.ts
    const nativeAspectRatio = CANVASWIDTH / CANVASHEIGHT;

    const currentAspectRatio = maxAvailableWidth / maxAvailableHeight;
    let desiredWidth: number;
    let desiredHeight: number;
    if (currentAspectRatio > nativeAspectRatio) {
        desiredHeight = maxAvailableHeight;
        desiredWidth = desiredHeight * nativeAspectRatio;
    } else {
        desiredWidth = maxAvailableWidth;
        desiredHeight = desiredWidth / nativeAspectRatio;
    }
    //Performance improvement to prevent resizing when dimensions have barely (or not) changed
    if (Math.abs(this._canvas.width - desiredWidth) > 0.25) {
        this._canvasScale.x = desiredWidth / CANVASWIDTH;
        this._canvasScale.y = desiredHeight / CANVASHEIGHT;

        //_canvas is the HTMLCanvasElement
        this._canvas.width = desiredWidth;
        this._canvas.height = desiredHeight;
        //S_Context is the CanvasRenderingContext2D for the canvas
        //_canvasScale is just a holder of two numbers, x amp; y, for use when we draw on the canvas
        S_Context.scale(this._canvasScale.x, this._canvasScale.y);
        //S_AccessibilityHandler is a custom accessibility framework that builds divs,
        //associates them with things in the canvas, and moves the divs around
        //whenever the corresponding thing moves.
        //They're designed to be visible to screen readers only.
        //This has caused a lot of bugs in the past, but usually by stretching the viewport too wide
        S_AccessibilityHandler.setOffset(this._canvas.offsetLeft, this._canvas.offsetTop);
    }
}

window.onresize = (event: UIEvent) => {
    this.scaleCanvasToAvailableSpace(window.innerWidth, window.innerHeight);
};
this.scaleCanvasToAvailableSpace(window.innerWidth, window.innerHeight);
//Hack to handle how some mobile browsers believe that an address bar is part of the inner window,
//even though it clearly isn't
//We recalculate the available height after the browser has finished sliding in the address bar
//This causes a flash 1 second after the page loads on certain browsers
//This fix is for an entirely separate canvas resizing bug
//where the canvas content was overflowing off the bottom of the window,
//and the overflow height was always equal to the address bar height
setTimeout(() => {
    this.scaleCanvasToAvailableSpace(window.innerWidth, window.innerHeight);
}, 1000);
 

CSHTML:

 @model TestPartnership.Web.Controllers.ViewModels.Mindmetriq.InitialLoadViewModel
@using System.Globalization
@using System.Web.Optimization
@using TestPartnership.Core.Helpers
@using TestPartnership.Localisation
@using TestPartnership.Web.Utilities
@{
    Layout = null;
    var skin = SessionManager.GetSkin();
    var favIconPath = skin == null ? "/Content/Images/favicon.png" : skin.FavIconPath;
}

<html lang="@SessionManager.GetLanguage().GetHtmlLangAttribute()">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>@Model.Title</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0" />
    <link rel="shortcut icon" sizes="32x32" href="@favIconPath" />

    @*----Apple Junk Start----*@
    <link rel="apple-touch-icon" href="@favIconPath" />
    <link rel="apple-touch-icon" sizes="120x120" href="@favIconPath" />
    <link rel="apple-touch-icon" sizes="152x152" href="@favIconPath" />
    <link rel="apple-touch-icon-precomposed" sizes="120x120" href="@favIconPath" />
    <link rel="apple-touch-icon-precomposed" sizes="152x152" href="@favIconPath" />
    @*------Apple Junk End------*@

    <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
    <link href="~/Styles/Framework/mindmetriq-base.css" rel="stylesheet">
</head>
<body>
<canvas id="mindmetriq" width="459" height="816">@TranslationHelper.CandidateMindmetriqHtml5Message</canvas>    

@Scripts.Render("~/MindmetriQ")

<script>
window.onload = (function() {
    // ReSharper disable once UseOfImplicitGlobalInFunctionScope
    window.mindmetriq = new Main("@Model.Name",
        "@(Model.Language)",
        "@(Model.BaseSubmitUrl)",
        "@(Model.BaseAssetUrl)",
         @(Model.AssessmentSectionId),
         @(Model.SkipTutorial.ToJsString()),
         @(Model.ExtraTimeFactor.ToString("F2", CultureInfo.InvariantCulture))
         @if (Model.TimeBoostsRecordedOnServer.HasValue)
         {
             <text>,</text>@(Model.TimeBoostsRecordedOnServer.Value)
         }
         @if (Model.BoostRecordedForCurrentQuestion.HasValue)
         {
             <text>,</text>@(Model.BoostRecordedForCurrentQuestion.Value.ToJsString())
         }
    );
    window.mindmetriq.run();
});
</script>
</body>
</html>
 

CSS:

 body,
html {
    touch-action: manipulation;
    font-family: 'Roboto';
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
}

canvas {
    background-color: #f2f2f2;
    border: 1px solid #78909c;
    position: absolute;
    left: 0;
    right: 0;
    margin: 0 auto;
    padding: 0;
    -ms-touch-action: pinch-zoom;
    touch-action: pinch-zoom;
}

div.mindmetriq-accessibility {
    position: absolute;
    background: transparent;
    pointer-events: none;
    touch-action: none;
}
 

Есть ли у вас какие-либо идеи, почему некоторые браузеры (а не другие) неправильно масштабируют содержимое холста?