Некоторое время назад я работал над мини-программами WeChat и столкнулся со многими подводными камнями, среди которых я столкнулся с необходимостью сохранения составных картинок фронтенда в альбом для раздачи в кругу друзей. Используйте краткую книгу, чтобы записать окончательное решение, сначала посмотрите на окончательный эффект
234ADFADB11E71B63B6C8B88DE493547.pngВесь демонстрационный код для этой статьи размещен на github,кодовый адрес, посетите в инструменте отладки WeChat
关闭合法域名检查
,开启es6转换
, пожалуйста, включите отладку для реальной отладки машиныvconsole
Проблемы, рассматриваемые в данной статье, следующие:
- Апплет WeChat генерирует изображения и сохраняет их в альбомы
- Апплет WeChat генерирует изображения для обеспечения отзывчивости
- Как добавить анимацию CSS на холст в собственном компоненте холста апплета WeChat
- Сохранить план совместного использования HD
- Апплет WeChat генерирует изображения для адаптации к одному экрану
Апплет WeChat генерирует изображения и сохраняет их в альбомы
Прежде всего, мы надеемся добиться следующих функций, нажмите на аватар пользователя, снизу появится всплывающее окно обмена, вы можете сохранить составное изображение в альбом, и вы можете закрыть всплывающий слой.
Мы инкапсулируем эту функцию в пользовательский компонент Component.
Определить базовую структуру wxml
<view class="share {{visible ? 'show' : ''}}">
<view class="content">
<canvas class="canvas" canvas-id="share" />
<view class="footer">
<view class="save">保存到相册</view>
<view class="close">关闭</view>
</view>
</view>
</view>
определить стиль wxss
.share {
position: fixed;
top: 0;
left: 0;
min-height: 100vh;
width: 100%;
background: rgba(61, 61, 61, 0.5);
visibility: hidden;
opacity: 0;
transition: opacity 0.2s ease-in-out;
z-index: 99999;
}
.share.show {
visibility: visible;
opacity: 1;
}
.share .content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.share .content .footer {
width: 562rpx;
height: 100rpx;
background: #fff;
border-top: 2rpx solid #e9e9e9;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
font-size: 28rpx;
}
.share .content .footer .close {
width: 100rpx;
height: 100rpx;
line-height: 100rpx;
flex-grow: 0;
flex-shrink: 0;
text-align: center;
border-left: 2rpx solid #e9e9e9;
}
.share .content .footer .save {
height: 100rpx;
line-height: 100rpx;
flex-grow: 1;
flex-shrink: 1;
text-align: center;
}
.share.show .content .canvas {
display: inline-block;
}
.share .content .canvas {
display: inline-block;
background: #fff;
margin: 60rpx 0 0 0;
width: 562rpx;
height: 1000rpx;
}
определить json
{
"component": true
}
Определить конструктор компонента
Component({
properties: {
visible: {
type: Boolean,
value: false
},
// 由于需要绘制用户信息,由页面传入
userInfo: {
type: Object,
value: false
}
},
methods: {
draw() {
// 实际绘制函数,后续绘制代码放于此处
}
}
})
Основные определения структуры и стиля завершены. Далее мы можем начать наше путешествие по рисованию. Нам нужно использовать функцию wx.getImageInfo апплета WeChat для синтеза изображений. Сначала мы обещаем это, чтобы облегчить последующие вызовы.
function getImageInfo(url) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: url,
success: resolve,
fail: reject,
})
})
}
Подготовительные работы завершены, приступаем к определению метода рисования.
const { userInfo } = this.data
const { avatarUrl, nickName } = userInfo
// 获取头像图像信息
const avatarPromise = getImageInfo(avatarUrl)
// 获取背景图像信息
const backgroundPromise = getImageInfo('https://img.xiaomeipingou.com/_assets_home-share-bg.jpg')
Promise.all([avatarPromise, backgroundPromise])
.then(([avatar, background]) => {
// 创建绘图上下文
const ctx = wx.createCanvasContext('share', this)
const canvasWidth = 281
const canvasHeight = 500
// 绘制背景,填充满整个canvas画布
ctx.drawImage(background.path, 0, 0, canvasWidth, canvasHeight)
const avatarWidth = 60
const avatarHeight = 60
const avatarTop = 40
// 绘制头像
ctx.drawImage(
avatar.path,
canvasWidth / 2 - avatarWidth / 2,
avatarTop - avatarHeight / 2,
avatarWidth,
avatarHeight
)
// 绘制用户名
ctx.setFontSize(20)
ctx.setTextAlign('center')
ctx.setFillStyle('#ffffff')
ctx.fillText(
nickName,
canvasWidth / 2,
avatarTop + 50,
)
ctx.stroke()
// 完成作画
ctx.draw()
})
Далее нам нужно следить за изменением свойства visible и решать, начинать ли рисовать
Component({
properties: {
visible: {
type: Boolean,
value: false,
observer(visible) {
// 当开始显示分享弹窗时开始绘制
if (visible) {
this.draw()
}
}
},
},
....省略其他代码
})
К этому моменту внешний рисунок в основном сформирован, и составное изображение можно увидеть при запуске апплета.Поскольку размер нашего рисунка основан на iphone6s, при просмотре с тем же разрешением на iphone6s и некоторых частях, размеры точно такие же, и проблем нет.Однако, когда мы открываем его с iphone6s plus или другими мобильными телефонами с разными разрешениями, это становится следующим
4C4A9E90-54B7-429D-A47C-1F1599E242F7.png
Нарисованное изображение не полностью покрыто, почему? Это второй вопрос встречен вопросом
Апплет WeChat генерирует изображения для обеспечения отзывчивости
На самом деле, все наши единицы ширины и высоты холста основаны на единицах rpx, поэтому на мобильных телефонах с разными разрешениями фактический размер отличается, но размер изображений, которые мы рисуем, все в единицах px, что, естественно, не может быть отзывчивым, поэтому нам нужен метод js для преобразования значения rpx в значение px.
Интерпретируя официальные документы WeChat, мы определяем простой метод преобразования следующим образом.
function createRpx2px() {
const { windowWidth } = wx.getSystemInfoSync()
return function(rpx) {
return windowWidth / 750 * rpx
}
}
const rpx2px = createRpx2px()
После определения функции преобразования единиц измерения нам нужно преобразовать только соответствующие значения.
const { userInfo } = this.data
const { avatarUrl, nickName } = userInfo
// 获取头像图像信息
const avatarPromise = getImageInfo(avatarUrl)
// 获取背景图像信息
const backgroundPromise = getImageInfo('https://img.xiaomeipingou.com/_assets_home-share-bg.jpg')
Promise.all([avatarPromise, backgroundPromise])
.then(([avatar, background]) => {
// 创建绘图上下文
const ctx = wx.createCanvasContext('share', this)
const canvasWidth = rpx2px(281 * 2)
const canvasHeight = rpx2px(500 * 2)
// 绘制背景,填充满整个canvas画布
ctx.drawImage(background.path, 0, 0, canvasWidth, canvasHeight)
const avatarWidth = rpx2px(60 * 2)
const avatarHeight = rpx2px(60 * 2)
const avatarTop = rpx2px(40 * 2)
// 绘制头像
ctx.drawImage(
avatar.path,
canvasWidth / 2 - avatarWidth / 2,
avatarTop - avatarHeight / 2,
avatarWidth,
avatarHeight
)
// 绘制用户名
ctx.setFontSize(rpx2px(20 * 2))
ctx.setTextAlign('center')
ctx.setFillStyle('#ffffff')
ctx.fillText(
nickName,
canvasWidth / 2,
avatarTop + rpx2px(50 * 2),
)
ctx.stroke()
// 完成作画
ctx.draw()
В настоящее время мобильный телефон может нормально отображаться независимо от разрешения.
Как добавить анимацию CSS на холст в собственном компоненте холста апплета WeChat
Все мы знаем, что канва апплета WeChat является нативным компонентом, и на нативный компонент наложено много ограничений, например нельзя использовать анимацию css, официальный документ выглядит следующим образом:
91AA5F90-D526-45FD-91B4-7CEB030BCA9F.png
Во-первых, мы пытаемся добавить всплывающую анимацию к родительской метке холста View.content label и изменить стиль следующим образом:
.share .content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
// 新增动画控制
transform: translate3d(0, 100%, 0);
transition: transform 0.2s ease-in-out;
}
// 新增动画控制
.share.show .content {
transform: translate3d(0, 0, 0);
}
При использовании в отладчике все нормально, снизу всплывает именно так, как и ожидалось, а потом исчезает, но при отладке на реальной машине эффект холстовой части становится менее плавным, гладким, без всплывающих окон. вверх анимация, без затухания Эффект, все стало таким жестким, так что же нам делать?
Идея решения заключается в следующем:
- Предоставьте тег холста, который нельзя скрыть (скрытый приведет к сбою рисования), удалите экран через свойство css tansform, чтобы сделать его невидимым.
- Используйте тег изображения вместо тега холста, чтобы отобразить его пользователю для просмотра.
- Когда холст нарисован, мы сохраняем нарисованное изображение во временную директорию и получаем адрес изображения
- Укажите адрес тега изображения для отображения
Основываясь на вышеизложенных идеях, сначала преобразуйте структуру нашего документа.
<view class="share {{ visible ? 'show' : '' }}">
<canvas class="canvas-hide" canvas-id="share" />
<view class="content">
<image class="canvas" src="{{imageFile}}" />
<view class="footer">
<view class="save">保存到相册</view>
<view class="close" bindtap="handleClose">关闭</view>
</view>
</view>
</view>
добавить стиль
.share .canvas-hide {
position: fixed;
top: 0;
left: 0;
transform: translateX(-100%);
width: 562rpx;
height: 1000rpx;
}
Чтобы сохранить изображение, нарисованное холстом, во временный каталог, нам нужно использовать API-интерфейс wx.canvasToTempFilePath апплета WeChat, поэтому сначала мы все еще обещаем его
function canvasToTempFilePath(option, context) {
return new Promise((resolve, reject) => {
wx.canvasToTempFilePath({
...option,
success: resolve,
fail: reject,
}, context)
})
}
Добавьте imageFile в атрибут данных компонента
// 仅列出新增部分,省略之前的代码
Component({
data: {
imageFile: ''
}
})
Измените наш метод рисования
// 仅列出新增部分,省略之前的代码
// 修改画布的draw函数如下
ctx.draw(false, () => {
canvasToTempFilePath({
canvasId: 'share',
}, this).then(({ tempFilePath }) => this.setData({ imageFile: tempFilePath }))
})
На данный момент, запустив отладку на реальной машине, мы видим, что она полностью соответствует нашим потребностям (самодовольным)
Сохранить план совместного использования HD
Затем нам нужно сохранить его в альбом, чтобы поделиться с кругом друзей или другим Weibo.
Чтобы сохранить изображение в альбом, нужно вызвать API апплета WeChat, wx.saveImageToPhotosAlbum, и Promise его согласно соглашению
function saveImageToPhotosAlbum(option) {
return new Promise((resolve, reject) => {
wx.saveImageToPhotosAlbum({
...option,
success: resolve,
fail: reject,
})
})
}
Мы добавили событие клика для сохранения альбомов
<view class="save" bindtap="handleSave">保存到相册</view>
Наконец, определите наш метод сохранения
// 仅列出新增部分,省略之前的代码
Component({
methods: {
handleSave() {
const { imageFile } = this.data
if (imageFile) {
saveImageToPhotosAlbum({
filePath: imageFile,
}).then(() => {
wx.showToast({
icon: 'none',
title: '分享图片已保存至相册',
duration: 2000,
})
})
}
}
}
})
На данный момент реализована функция сохранения в альбом, но есть некоторые недочеты.Изначально картинки которые мы рисовали очень высокого разрешения.Картинки которые можно сохранить после рисования становятся размытыми,и они не такие высокие -определение.
Как сделать так, чтобы сохраненная картинка не была искажена?Можно рассмотреть увеличение размера холста до 3-х раз и рисование в 3 раза больше размера картинки.
Изменить стиль
.share .content .canvas {
display: inline-block;
background: #fff;
margin: 60rpx 0 0 0;
width: 1686rpx; // 修改为之前的3倍
height: 3000rpx; // 修改为之前的3倍
}
Измените функцию рисования, чтобы увеличить размер рисунка в 3 раза.
const { userInfo } = this.data
const { avatarUrl, nickName } = userInfo
// 获取头像图像信息
const avatarPromise = getImageInfo(avatarUrl)
// 获取背景图像信息
const backgroundPromise = getImageInfo('https://img.xiaomeipingou.com/_assets_home-share-bg.jpg')
Promise.all([avatarPromise, backgroundPromise])
.then(([avatar, background]) => {
// 创建绘图上下文
const ctx = wx.createCanvasContext('share', this)
const canvasWidth = rpx2px(281 * 2 * 3) // 扩大3倍
const canvasHeight = rpx2px(500 * 2 * 3) // 扩大3倍
// 绘制背景,填充满整个canvas画布
ctx.drawImage(background.path, 0, 0, canvasWidth, canvasHeight)
const avatarWidth = rpx2px(60 * 2 * 3) // 扩大3倍
const avatarHeight = rpx2px(60 * 2 * 3) // 扩大3倍
const avatarTop = rpx2px(40 * 2 * 3) // 扩大3倍
// 绘制头像
ctx.drawImage(
avatar.path,
canvasWidth / 2 - avatarWidth / 2,
avatarTop - avatarHeight / 2,
avatarWidth,
avatarHeight
)
// 绘制用户名
ctx.setFontSize(rpx2px(20 * 2 * 3)) // 扩大3倍
ctx.setTextAlign('center')
ctx.setFillStyle('#ffffff')
ctx.fillText(
nickName,
canvasWidth / 2,
avatarTop + rpx2px(50 * 2 * 3), // 扩大3倍
)
ctx.stroke()
// 完成作画
ctx.draw(false, () => {
canvasToTempFilePath({
canvasId: 'share',
}, this).then(({ tempFilePath }) => this.setData({ imageFile: tempFilePath }))
})
Мы пересохранили картинку и обнаружили, что картинка стала высокой четкости, ху~~~
Наконец, мы можем с радостью отправить результаты на небольшой тест, вроде бы все идет хорошо, но, к сожалению, мы не можем пройти тест различных разрешений модели. так
1541671633246.jpg
Кнопка заблокирована, это беспомощно
Апплет WeChat генерирует изображения для адаптации к одному экрану
Мы надеемся, что содержимое общего всплывающего окна может быть полностью отображено на одном экране, тогда мы сможем найти коэффициент масштабирования на основе текущего соотношения сторон мобильного телефона и соотношения сторон размера проекта для масштабирования всего содержимого.
Определить вычисление масштабирования
// 仅列出新增部分,省略之前的代码
Component({
data: {
responsiveScale: 1, // 缩放比例默认为1
},
lifetimes: {
ready() {
const designWidth = 375
const designHeight = 603 // 这是在顶部位置定义,底部无tabbar情况下的设计稿高度
// 以iphone6为设计稿,计算相应的缩放比例
const { windowWidth, windowHeight } = wx.getSystemInfoSync()
const responsiveScale =
windowHeight / ((windowWidth / designWidth) * designHeight)
if (responsiveScale < 1) {
this.setData({
responsiveScale,
})
}
},
},
})
Изменить документ wxml
<view class="share {{ visible ? 'show' : '' }}">
<canvas class="canvas-hide" canvas-id="share" />
<view class="content" style="transform:scale({{responsiveScale}});-webkit-transform:scale({{responsiveScale}});">
<image class="canvas" src="{{imageFile}}" />
<view class="footer">
<view class="save" bindtap="handleSave">保存到相册</view>
<view class="close" bindtap="handleClose">关闭</view>
</view>
</view>
</view>
Изменить таблицу стилей wxss
.share .content {
// 省略其他定义
// 新增缩放中心控制为顶部中心
transform-origin: 50% 0;
}
Ямы, возникшие в общем обмене, были устранены, и есть много кодов. Все коды размещены на github. Добро пожаловать в гости и запуститькодовый адрес, только делая это самостоятельно, можно по-настоящему овладеть знаниями