Поездка в яму H5 "Сохранить страницу как картинку"

WeChat HTML Canvas

1. Спрос

В последнее время руководитель продукта Dr. Предыдущие изображения были сгенерированы серверной частью и кэшированы в течение трех дней, что привело к несвоевременному обновлению информации. Выполнение этого на переднем конце позволяет избежать этих проблем.

Как только я это услышал, то легко сказать, а не просто ли это функция для сохранения картинок, достаточно посмотреть требования:

  • Улучшите информацию о карте, и информация будет более трехмерной при обмене
  • Изменить запись профиля
  • сохранить запись изображения
  • Это может решить проблему времени кеша визитной карточки врача
  • Выглядит так ⬇

image
image

Проанализируйте две точки

  • html отображает информацию о пользователе в режиме реального времени
  • Нажмите «Сохранить», чтобы сохранить текущую страницу как изображение локально и не содержит функциональных кнопок.

2. Схема

Потому что я слышал раньше, что есть библиотека, которая может конвертировать HTML в холст, а потом я услышал, что холст можно преобразовать в картинки, а потом я услышал, что картинки можно скачать.... (Разработка в основном зависит от слуха (поиск ), это бред )

Тогда мой основной план:
html -> canvas -> image -> a[download]

  1. html2canvas.js: преобразование htmldom в элемент холста.портал
  2. canvasAPI: toDataUrl() может конвертировать холст в формат base64
  3. Создайте тег [download], чтобы инициировать событие клика для загрузки.

3. Производительность ямы

Теперь, когда план определен, давайте начнем выходить на пит-лейн и выступать, 👏

3.1 Принцип

Официальное введение выглядит следующим образом:

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

// v0.4.1
html2canvas(element, {
    onrendered: function(canvas) {
        // 现在你已经拿到了canvas DOM元素    
    }
});

// v0.5.0
html2canvas(element, options).then(canvas => {
    // 现在你已经拿到了canvas DOM元素    
});

Итак, в принципе можно догадаться, что весь рабочий процесс должен быть:

  1. Рекурсивно обработайте каждый узел и запишите, как он должен быть нарисован. (Например, div рисует границы и фон, text рисует текст и т. д.)
  2. Рассмотрим иерархию узлов. Например, влияние многих атрибутов стиля, связанных с макетом: z-index, float, position и т. д.
  3. Картина с низкого уровня до холста и нарисовать. Высокий уровень высокого покрытия низкий (процесс рендеринга самого браузера очень похож).

3.2 Яма 💀

В настоящее время существует много официальных версий, официальная версияv0.4.1 - 7.9.2013, последняя версияv0.5.0-beta4, то для нашего развития, если мы не играемся с новыми фичами что ли, мы обычно выбираем официальную версию.В итоге мы попали в первую яму и долго лезли.

3.2.1 Изображение размыто

Потому что во время разработки для создания холста использовался симулятор хрома, и размытие не было обнаружено, но когда ПК использовался для запроса ресурсов разработки с мобильного телефона, было обнаружено, что размытие экрана было очень очевидным.

Как показано на рисунке:

image
image

Легко подумать, что это может быть проблема расчета плотности пикселей на мобильном терминале.

Соотношение пикселей устройства (называемое dpr) определяет соответствие между физическими пикселями и независимыми от устройства пикселями.Его значение можно получить по следующей формуле:

设备像素比 = 物理像素 / 设备独立像素 // 在某一方向上,x方向或者y方向

Знать это бесполезно, потому что в документации вообще нет места для настройки соотношения пикселей. .

Однако в ходе исследований выясняется, что официальные документы на самом деле до сих пор0.4.1да, из0.5.0Фактически, с самого начала версии он тайно поддерживает пользовательский холст в качестве элемента конфигурации, и он начнет рисовать на основе холста, который мы передали. Поэтому, когда мы вызываем html2canvas, мы можем сначала создать холст подходящего размера, а затем передать его.

Без лишних слов сначала обновите библиотеку до0.5.0,Потом:

/**
 * 根据window.devicePixelRatio获取像素比
 */
function DPR() {
    if (window.devicePixelRatio && window.devicePixelRatio > 1) {
        return window.devicePixelRatio;
    }
    return 1;
}
/**
 *  将传入值转为整数
 */
function parseValue(value) {
    return parseInt(value, 10);
};
/**
 * 绘制canvas
 */
async function drawCanvas(selector) {
    // 获取想要转换的 DOM 节点
    const dom = document.querySelector(selector);
    const box = window.getComputedStyle(dom);
    // DOM 节点计算后宽高
    const width = parseValue(box.width);
    const height = parseValue(box.height);
    // 获取像素比
    const scaleBy = DPR();
    // 创建自定义 canvas 元素
    const canvas = document.createElement('canvas');

    // 设定 canvas 元素属性宽高为 DOM 节点宽高 * 像素比
    canvas.width = width * scaleBy;
    canvas.height = height * scaleBy;
    // 设定 canvas css宽高为 DOM 节点宽高
    canvas.style.width = `${width}px`;
    canvas.style.height = `${height}px`;
    // 获取画笔
    const context = canvas.getContext('2d');

    // 将所有绘制内容放大像素比倍
    context.scale(scaleBy, scaleBy);

    // 将自定义 canvas 作为配置项传入,开始绘制
    return await html2canvas(dom, {canvas});
}

Приведенный выше код сначала получает соотношение пикселей устройства и создает холст большего размера в соответствии с соотношением. Например, двойной экран - это двойной, тройной экран - это тройной, восемь раз зеркало - это восемь раз...
Скриншот на мобильном телефоне такой же, как эффект отображения html, и разница практически незаметна.

image
image

3.2.2 Почему картинка отсутствует?

Скриншот с ПК:

image
image

Причины могут быть разные.После расследования выясняется, что картинки в канве являются междоменными.объяснил здесь
В общем, так оно и есть: вы можете рисовать междоменные картинки в канвасе, но в это время канвас находится в «загрязненном» состоянии, а загрязненный канвас вызовет проблемы при использовании таких API, как toDataUrl().

Итак, теперь нам нужно сделать две вещи:

  1. установить элемент imgcrossOriginсвойство, значениеanonymous
  2. Настройки сервера изображений разрешают междоменное использование (возврат заголовка CORS)

Первое сделать несложно, т.к. сам html2canvas поддерживает настройкуuseCORS: true;

А вот второе зависит от ситуации. Когда изображение размещается на собственном сервере, достаточно попросить администратора изменить конфигурацию. Но когда изображение помещается в CDN... Что ж, для более быстрого ответа многие CDN кэшируют возвращаемое значение изображения, а кэшированное значение не имеет заголовка CORS. Поскольку заголовок CORS отсутствует, запрос js будет перехвачен. В настоящее время мы можем использовать переадресацию сервера с заголовками CORS при пересылке. (Хорошим решением является использование среднего уровня узла на внешнем интерфейсе для переадресации сервера, что будет обсуждаться отдельно в следующий раз)

В ПОРЯДКЕ. Используя приведенную выше схему, протестируем ее.

Сторона ПК открывается, отлично.

WeChat, эй, все еще не работает.
Позже нашел, используя html2canvas0.5.0С версией проблем нет, но она используется при разработке0.4.1Рисование на холсте все равно приведет к потере изображения. Предположение, потому что html2canvas имеет что-то неописуемое, когда он предварительно загружает изображения и рисует изображения. Чтобы решить эту проблему, мы используем очень жесткое решение: используем js, чтобы получить изображение, получить его base64, поместить его обратно в src img, а затем отрисовать.

/**
 * 图片转base64格式
 */
img2base64(url, crossOrigin) {
    return new Promise(resolve => {
        const img = new Image();

        img.onload = () => {
            const c = document.createElement('canvas');

            c.width = img.naturalWidth;
            c.height = img.naturalHeight;

            const cxt = c.getContext('2d');

            cxt.drawImage(img, 0, 0);
            // 得到图片的base64编码数据
            resolve(c.toDataURL('image/png'));
        };

        crossOrigin && img.setAttribute('crossOrigin', crossOrigin);
        img.src = url;
    });
}

В эту яму наконец-то наткнулись.

3.2.3 Фаска

border-radiusДолжна быть ≤ половины длины короткой стороны и иметь конкретное значение, иначе могут возникнуть фантастические эффекты.

Кроме того, использование псевдоэлементов для достижения границ в 0,5 пикселя также может иметь прекрасные эффекты, рекомендуется использовать их напрямую.borderАтрибуты

0.4.1В версии круглое изображение можно установить только как фоновое изображение, img не поддерживает отрисовку border-radius,0.5.0Нет такого ограничения в

3.2.4 Пунктирная линия

Как упоминалось ранее, html2canvas поддерживает не все свойства css. использоватьborder-style: dashed/dottedНедействительная, все еще большая сплошная линия. Вырезанное изображение допустимо на ПК, но в WeChat при попытке использовать вырезанное изображение для рендеринга пунктирной линии может по-прежнему сообщатьсяSecurityError, The operation is insecure.Ошибка, приводящая к сбою преобразования в base64

3.3 Сохранить

идеально:

/**
 * 在本地进行文件保存
 * @param  {String} data     要保存到本地的图片数据
 * @param  {String} filename 文件名
 */
saveFile(data, filename) {
    const save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
    save_link.href = data;
    save_link.download = filename;

    const event = document.createEvent('MouseEvents');
    event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    save_link.dispatchEvent(event);
}

Реальность:

ПК: Отлично.Босс WeChat: Извините, что вы сказали? Я не слышу? !

Ну, в WeChat вообще ничего не происходит. ПроверятьWeChat SDKВыяснил:

  • downloadImageподдерживается толькоuploadImageИзображение, загруженное интерфейсом.
  • uploadImageИнтерфейс поддерживает толькоchooseImageКартинка, выбранная интерфейсным альбомом.
  • chooseImageИнтерфейс предназначен для выбора изображений из локального альбома.
  • Так вот вопрос, фотки все в альбоме, что еще надо сделать?
  • ....

4. Доставить (с болью и компромиссом)

Окончательное решение:

  • Пользователь заходит на эту страницу
  • Получите всю информацию о текущем пользователе, аватаре, QR-коде и т. д.
  • Преобразовать все изображения в base64
  • оказыватьhtml
  • рисоватьcanvas
  • сохранить холст как base64
  • заменятьhtmlзаimg,srcкак base64
  • После завершения преобразования страницы в изображение пользователи WeChat могут долго нажимать на страницу, чтобы вызвать actionSheet для идентификации или сохранения изображения.

То есть, когда пользователь впервые заходит на страницу, отображается html. После выполнения js удалите исходный html и замените его картинками.

Вернемся к нашим потребностям:

  • html отображать информацию о пользователе в режиме реального времени
  • Нажмите «Сохранить», чтобы локально сохранить текущую страницу в виде изображения.

На самом деле окончательно реализован только первый пункт, а второй пункт фактически реализован наполовину.Хотя картинка сгенерирована, функция сохранения по-прежнему требует от пользователя долгого нажатия на картинку и вызова встроенного меню WeChat для завершения . При разработке H5 после рассмотрения WeChat могут возникнуть некоторые проблемы и ограничения, которые ранее не учитывались, и продакт-менеджеры и программисты должны знать об этом как можно больше. Знайте, что можно и чего нельзя делать в WeChat, снижая затраты на разработку и повторное общение.

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

Автор: Сиреневый сад f2e - Гу Чунси