著者は Web マインドマップmind-mapをオープンソース化しています. 最近, 背景画像の効果を最適化する際に問題が発生しました. ページに表示するときは をcss
使用して背景画像をbackground-image
レンダリングしますが、エクスポートすると背景画像が背景画像の背景画像は、サイズ、位置、繰り返しの設定など、より豊富な効果をサポートしてcanvas
いますが、著者は1つの方法しか見つけられず、繰り返しの設定のみをサポートしています効果なので、あるものをシミュレートする方法 背景効果については、これで終わりではなく、次に一緒に試してみましょう。css
background-size
background-position
background-repeat
canvas
createPattern()
canvas
css
最初に説明することは、すべての効果を完全かつ完全に100%
シミュレートするわけではないということです.強力すぎるため、属性値の組み合わせは非常に柔軟であり、多くの種類があり、その中にはユニットの種類が多く、したがって、いくつかの一般的な状況のみがシミュレートされ、ユニットのみが考慮されます。css
css
px
%
canvas
ちなみに、この記事を読んだ後、バックグラウンドで設定されたいくつかの属性の方法drawImage
と使用方法も確認できますcss
。
canvas の drawImage() メソッド
一般に、canvas
このdrawImage()
メソッドを使用して背景画像を描画します. 最初にこのメソッドを見てみましょう. このメソッドはさらにパラメータを受け取ります:
必要なパラメーターは 3 つだけです。
基本的なフレームワークとツール
コア ロジックは、画像をロードし、メソッドを使用して画像を描画することです。これは、さまざまな属性と値に従って計算されたパラメーターdrawImage
にすぎないため、関数の次の基本的なフレームワークを記述できます。css
drawImage
const drawBackgroundImageToCanvas = (
ctx,// canvas绘图上下文
width,// canvas宽度
height,// canvas高度
img,// 图片url
{
backgroundSize, backgroundPosition, backgroundRepeat }// css样式,只模拟这三种
) => {
// canvas的宽高比
let canvasRatio = width / height
// 加载图片
let image = new Image()
image.src = img
image.onload = () => {
// 图片的宽高及宽高比
let imgWidth = image.width
let imgHeight = image.height
let imageRatio = imgWidth / imgHeight
// 绘制图片
// drawImage方法的参数值
let drawOpt = {
sx: 0,
sy: 0,
swidth: imgWidth,// 默认绘制完整图片
sheight: imgHeight,
x: 0,
y: 0,
width: imgWidth,// 默认不缩放图片
height: imgHeight
}
// 根据css属性和值计算...
// 绘制图片
ctx.drawImage(image, drawOpt.sx, drawOpt.sy, drawOpt.swidth, drawOpt.sheight, drawOpt.x, drawOpt.y, drawOpt.width, drawOpt.height)
}
}
次に、いくつかのツール機能を見てみましょう。
// 将以空格分隔的字符串值转换成成数字/单位/值数组
const getNumberValueFromStr = value => {
let arr = String(value).split(/\s+/)
return arr.map(item => {
if (/^[\d.]+/.test(item)) {
// 数字+单位
let res = /^([\d.]+)(.*)$/.exec(item)
return [Number(res[1]), res[2]]
} else {
// 单个值
return item
}
})
}
css
の属性値は文字列型や数値型などで、100px 100% auto
そのまま使うと不便なので[[100, 'px'], [100, '%'], 'auto']
フォームに変換します。
// 缩放宽度
const zoomWidth = (ratio, height) => {
// w / height = ratio
return ratio * height
}
// 缩放高度
const zoomHeight = (ratio, width) => {
// width / h = ratio
return width / ratio
}
元の比率と新しい幅または高さに基づいて、スケーリングされた幅または高さを計算します。
background-size プロパティをシミュレートする
デフォルトbackground-repeat
値は ですrepeat
。重複の場合は考慮しないため、 に設定しますno-repeat
。
background-size
この属性は、背景画像のサイズを設定するために使用され、順番にシミュレートされる 4 種類の値を受け入れることができます。
長さタイプ
背景画像の高さと幅を設定します。最初の値は幅を設定し、2 番目の値は高さを設定します。値が 1 つだけ指定されている場合、2 番目の値はデフォルトでauto (自動) になります。
css
スタイルは次のとおりです。
.cssBox {
background-image: url('/1.jpg');
background-repeat: no-repeat;
background-size: 300px;
}
値が 1 つだけ設定されている場合は、背景画像表示の実際の幅を表します。高さが設定されていない場合は、画像の縦横比に応じて自動的にスケーリングされます。効果は次のとおりです。
のシミュレーションは非常に単純で、4 つのパラメーターをメソッドcanvas
に渡す必要があります: 、画像を表す、キャンバスに画像を配置する位置を表す、特別な設定はありません。サイズ,値が1つしか渡されない場合,この値に直接設定し,画像の縦横比に応じて計算します. 2つの値が渡された場合, 2つの値を別々に渡します. さらに, あなたは必要です次のように値を処理します。drawImage
img、x、y、width、height
img
x、y
0、0
width、height
background-size
width
height
width、height
auto
drawBackgroundImageToCanvas(ctx, width, height, this.img, {
backgroundSize: '300px'
})
const drawBackgroundImageToCanvas = () =>{
// ...
image.onload = () => {
// ...
// 模拟background-size
handleBackgroundSize({
backgroundSize,
drawOpt,
imageRatio
})
// ...
}
}
// 模拟background-size
const handleBackgroundSize = ({
backgroundSize, drawOpt, imageRatio }) => {
if (backgroundSize) {
// 将值转换成数组
let backgroundSizeValueArr = getNumberValueFromStr(backgroundSize)
// 两个值都为auto,那就相当于不设置
if (backgroundSizeValueArr[0] === 'auto' && backgroundSizeValueArr[1] === 'auto') {
return
}
// 图片宽度
let newNumberWidth = -1
if (backgroundSizeValueArr[0]) {
if (Array.isArray(backgroundSizeValueArr[0])) {
// 数字+单位类型
drawOpt.width = backgroundSizeValueArr[0][0]
newNumberWidth = backgroundSizeValueArr[0][0]
} else if (backgroundSizeValueArr[0] === 'auto') {
// auto类型,那么根据设置的新高度以图片原宽高比进行自适应
if (backgroundSizeValueArr[1]) {
drawOpt.width = zoomWidth(imageRatio, backgroundSizeValueArr[1][0])
}
}
}
// 设置了图片高度
if (backgroundSizeValueArr[1] && Array.isArray(backgroundSizeValueArr[1])) {
// 数字+单位类型
drawOpt.height = backgroundSizeValueArr[1][0]
} else if (newNumberWidth !== -1) {
// 没有设置图片高度或者设置为auto,那么根据设置的新宽度以图片原宽高比进行自适应
drawOpt.height = zoomHeight(imageRatio, newNumberWidth)
}
}
}
効果は次のとおりです。
2 つの値を設定する効果:
background-size: 300px 400px;
パーセンテージ タイプ
背景に対するローカライズされた領域の割合が計算されます。最初の値は幅のパーセンテージを設定し、2 番目の値は高さのパーセンテージを設定します。値が 1 つだけ指定されている場合、2 番目の値はデフォルトでauto (自動) になります。たとえば、 set の場合、画像は背景領域の幅と高さ50% 80%
に合わせてスケーリングされます。50%
80%
css
スタイルは次のとおりです。
.cssBox {
background-image: url('/1.jpg');
background-repeat: no-repeat;
background-size: 50% 80%;
}
実装も非常に簡単. 上記に基づいて, 単位が であるかどうかを判断し, ある%
場合,canvas
画像の幅と高さに応じて表示される画像の幅と高さを計算します. 2番目の値は設定されていません.または、auto
以前と同様に、適応する画像の縦横比にも基づいています。
drawBackgroundImageToCanvas(ctx, width, height, this.img, {
backgroundSize: '50% 80%'
})
handleBackgroundSize({
backgroundSize,
drawOpt,
imageRatio,
canvasWidth: width,// 传参新增canvas的宽高
canvasHeight: height
})
// 模拟background-size
const handleBackgroundSize = ({
backgroundSize, drawOpt, imageRatio, canvasWidth, canvasHeight }) => {
if (backgroundSize) {
// ...
// 图片宽度
let newNumberWidth = -1
if (backgroundSizeValueArr[0]) {
if (Array.isArray(backgroundSizeValueArr[0])) {
// 数字+单位类型
if (backgroundSizeValueArr[0][1] === '%') {
// %单位,则图片显示的高度为画布的百分之多少
drawOpt.width = backgroundSizeValueArr[0][0] / 100 * canvasWidth
newNumberWidth = drawOpt.width
} else {
// 其他都认为是px单位
drawOpt.width = backgroundSizeValueArr[0][0]
newNumberWidth = backgroundSizeValueArr[0][0]
}
} else if (backgroundSizeValueArr[0] === 'auto') {
// auto类型,那么根据设置的新高度以图片原宽高比进行自适应
if (backgroundSizeValueArr[1]) {
if (backgroundSizeValueArr[1][1] === '%') {
// 高度为%单位
drawOpt.width = zoomWidth(imageRatio, backgroundSizeValueArr[1][0] / 100 * canvasHeight)
} else {
// 其他都认为是px单位
drawOpt.width = zoomWidth(imageRatio, backgroundSizeValueArr[1][0])
}
}
}
}
// 设置了图片高度
if (backgroundSizeValueArr[1] && Array.isArray(backgroundSizeValueArr[1])) {
// 数字+单位类型
if (backgroundSizeValueArr[1][1] === '%') {
// 高度为%单位
drawOpt.height = backgroundSizeValueArr[1][0] / 100 * canvasHeight
} else {
// 其他都认为是px单位
drawOpt.height = backgroundSizeValueArr[1][0]
}
} else if (newNumberWidth !== -1) {
// 没有设置图片高度或者设置为auto,那么根据设置的新宽度以图片原宽高比进行自适应
drawOpt.height = zoomHeight(imageRatio, newNumberWidth)
}
}
}
効果は次のとおりです。
カバータイプ
background-size
cover
画像が元の縦横比を維持し、背景の配置領域を完全にカバーする最小サイズにスケーリングされることを意味するように設定します。画像は変形されません。
css
スタイルは次のとおりです。
.cssBox {
background-image: url('/3.jpeg');
background-repeat: no-repeat;
background-size: cover;
}
この実装も非常に単純です. 画像の縦横比とcanvas
画像の縦横比に応じて, ズームされた画像の幅がcanvas
画像の幅と同じになるか, 画像の高さが画像canvas
と同じになります.写真の高さ。
drawBackgroundImageToCanvas(ctx, width, height, this.img, {
backgroundSize: 'cover'
})
handleBackgroundSize({
backgroundSize,
drawOpt,
imageRatio,
canvasWidth: width,
canvasHeight: height,
canvasRatio// 参数增加canvas的宽高比
})
const handleBackgroundSize = ({
backgroundSize,
drawOpt,
imageRatio,
canvasWidth,
canvasHeight,
canvasRatio
}) => {
// ...
// 值为cover
if (backgroundSizeValueArr[0] === 'cover') {
if (imageRatio > canvasRatio) {
// 图片的宽高比大于canvas的宽高比,那么图片高度缩放到和canvas的高度一致,宽度自适应
drawOpt.height = canvasHeight
drawOpt.width = zoomWidth(imageRatio, canvasHeight)
} else {
// 否则图片宽度缩放到和canvas的宽度一致,高度自适应
drawOpt.width = canvasWidth
drawOpt.height = zoomHeight(imageRatio, canvasWidth)
}
return
}
// ...
}
効果は次のとおりです。
タイプを含む
background-size
タイプに設定するとcontain
、画像は元の縦横比を維持し、背景の配置領域に適した最大サイズにスケーリングされます。つまり、画像は完全に表示されますが、必ずしも背景を覆うとは限りません。横と縦の一方向に余白があっても構いません。
css
スタイル:
.cssBox {
background-image: url('/1.jpg');
background-repeat: no-repeat;
background-size: contain;
}
実装はcover
型の実装とちょうど反対です. ピクチャのアスペクト比がピクチャcanvas
のアスペクト比よりも大きい場合, ピクチャを完全に表示するために, ピクチャの幅はの幅canvas
と一致しています.高さは自己適応型です。
const handleBackgroundSize = () => {
// ...
// 值为contain
if (backgroundSizeValueArr[0] === 'contain') {
if (imageRatio > canvasRatio) {
// 图片的宽高比大于canvas的宽高比,那么图片宽度缩放到和canvas的宽度一致,高度自适应
drawOpt.width = canvasWidth
drawOpt.height = zoomHeight(imageRatio, canvasWidth)
} else {
// 否则图片高度缩放到和canvas的高度一致,宽度自适应
drawOpt.height = canvasHeight
drawOpt.width = zoomWidth(imageRatio, canvasHeight)
}
return
}
}
効果は次のとおりです。
ここまでのbackground-size
シミュレーションは終わったので、見てみましょうbackground-position
。
background-position プロパティをシミュレートする
まず、background-size
設定されていない状況を見てください。
background-position
このプロパティは、背景画像の開始位置を設定するために使用されます。デフォルト値は です 0% 0%
。また、いくつかの異なるタイプの値をサポートしています。それらを 1 つずつ参照してください。
パーセンテージ タイプ
最初の値は水平位置を設定し、2 番目の値は垂直位置を設定します。左上隅は0%0%
、右下隅は です100%100%
。値が 1 つだけ設定されている場合、2 番目のデフォルトは です。たとえば50%
、 に設定されています。例: 画像の中心点と背景領域の中心点が一致していることを表します。50% 60%
50% 60%
50% 60%
50% 50%
css
スタイル:
.cssBox {
background-image: url('/2.jpg');
background-repeat: no-repeat;
background-position: 50% 50%;
}
実装に関しては,drawImage
メソッドimg
の3 つのパラメータのみを使用x、y
する必要があります. 画像の幅と高さはスケーリングされず, 画像に対応する距離は比率に従って計算されますcanvas
. それらの差はcanvas
画像の位置です.画像に表示される画像。
drawBackgroundImageToCanvas(ctx, width, height, this.img, {
backgroundPosition: '50% 50%'
})
const drawBackgroundImageToCanvas = () => {
// ...
// 模拟background-position
handleBackgroundPosition({
backgroundPosition,
drawOpt,
imgWidth,
imgHeight,
canvasWidth: width,
canvasHeight: height
})
// ...
}
// 模拟background-position
const handleBackgroundPosition = ({
backgroundPosition,
drawOpt,
imgWidth,
imgHeight,
canvasWidth,
canvasHeight
}) => {
if (backgroundPosition) {
// 将值转换成数组
let backgroundPositionValueArr = getNumberValueFromStr(backgroundPosition)
if (Array.isArray(backgroundPositionValueArr[0])) {
if (backgroundPositionValueArr.length === 1) {
// 如果只设置了一个值,第二个默认为50%
backgroundPositionValueArr.push([50, '%'])
}
// 水平位置
if (backgroundPositionValueArr[0][1] === '%') {
// 单位为%
let canvasX = (backgroundPositionValueArr[0][0] / 100) * canvasWidth
let imgX = (backgroundPositionValueArr[0][0] / 100) * imgWidth
// 计算差值
drawOpt.x = canvasX - imgX
}
// 垂直位置
if (backgroundPositionValueArr[1][1] === '%') {
// 单位为%
let canvasY = (backgroundPositionValueArr[1][0] / 100) * canvasHeight
let imgY = (backgroundPositionValueArr[1][0] / 100) * imgHeight
// 计算差值
drawOpt.y = canvasY - imgY
}
}
}
}
効果は次のとおりです。
長さタイプ
最初の値は水平位置を表し、2 番目の値は垂直位置を表します。左上隅は0 0
. 単位はpx
またはその他のcss
単位にすることができます。もちろん、 のみを考慮しますpx
。値が 1 つだけ指定されている場合、他の値は になります50%
。だから、ミックス%
してマッチさせることができますpx
。
css
スタイル:
.cssBox {
background-image: url('/2.jpg');
background-repeat: no-repeat;
background-position: 50px 150px;
}
この実装はより単純で、値をパラメーターに直接渡すだけdrawImage
ですx、y
。
drawBackgroundImageToCanvas(ctx, width, height, this.img, {
backgroundPosition: '50px 150px'
})
// 模拟background-position
const handleBackgroundPosition = ({
}) => {
// ...
// 水平位置
if (backgroundPositionValueArr[0][1] === '%') {
// ...
} else {
// 其他单位默认都为px
drawOpt.x = backgroundPositionValueArr[0][0]
}
// 垂直位置
if (backgroundPositionValueArr[1][1] === '%') {
// ...
} else {
// 其他单位默认都为px
drawOpt.y = backgroundPositionValueArr[1][0]
}
}
キーワード タイプ
left
、などのtop
キーワードを組み合わせることです。これは特別な値と見なすことができるため、これらのキーワードをパーセンテージ値にマップするためのマッピングを記述するだけで済みます。left top
center center
center bottom
%
.cssBox {
background-image: url('/2.jpg');
background-repeat: no-repeat;
background-position: right bottom;
}
drawBackgroundImageToCanvas(ctx, width, height, this.img, {
backgroundPosition: 'right bottom'
})
// 关键词到百分比值的映射
const keyWordToPercentageMap = {
left: 0,
top: 0,
center: 50,
bottom: 100,
right: 100
}
const handleBackgroundPosition = ({
}) => {
// ...
// 将关键词转为百分比
backgroundPositionValueArr = backgroundPositionValueArr.map(item => {
if (typeof item === 'string') {
return keyWordToPercentageMap[item] !== undefined
? [keyWordToPercentageMap[item], '%']
: item
}
return item
})
// ...
}
background-size と組み合わせる
background-size
最後に、 と を組み合わせるとどうなるかを見ていきます。
.cssBox {
background-image: url('/2.jpg');
background-repeat: no-repeat;
background-size: cover;
background-position: right bottom;
}
drawBackgroundImageToCanvas(ctx, width, height, this.img, {
backgroundSize: 'cover',
backgroundPosition: 'right bottom'
})
結果は次のとおりです。
矛盾している、なぜこれなのか? 整理してみましょう. まず、処理はパラメーター、つまり、その中に表示される画像の幅と高さをbackground-size
計算し、画像の幅と高さを処理に使用しますが、私たちが渡すものはまだ絵です の元の幅と高さ、もちろんこの計算には問題があります、それを修正してください:drawImage
width、height
canvas
background-position
// 模拟background-position
handleBackgroundPosition({
backgroundPosition,
drawOpt,
imgWidth: drawOpt.width,// 改为传计算后的图片的显示宽高
imgHeight: drawOpt.height,
imageRatio,
canvasWidth: width,
canvasHeight: height,
canvasRatio
})
効果をもう一度見てみましょう。
background-repeat プロパティをシミュレートする
background-repeat
この属性は、オブジェクトをどのように並べるかを設定するために使用されますbackground-image
。デフォルト値はrepeat
、つまり、イメージが背景領域より小さい場合、デフォルトで垂直方向および水平方向に繰り返されます。いくつかのオプション値があります:
repeat-x
: 水平位置のみが背景画像を繰り返しますrepeat-y
: 垂直位置のみ背景画像を繰り返しますno-repeat
:background-image
繰り返さない
次に、これらの状況を実装します。
繰り返しなし
まず、画像の幅と高さが背景領域よりも大きいかどうかを判断します. 大きい場合は、タイルまたは処理する必要はなく、他の値は処理する必要はありませんno-repeat
:
// 模拟background-repeat
handleBackgroundRepeat({
backgroundRepeat,
drawOpt,
imgWidth: drawOpt.width,
imgHeight: drawOpt.height,
imageRatio,
canvasWidth: width,
canvasHeight: height,
canvasRatio
})
ここでアップロードする画像の幅と高さも、計算されたbackground-size
画像表示の幅と高さであることがわかります。
// 模拟background-repeat
const handleBackgroundRepeat = ({
backgroundRepeat,
drawOpt,
imgWidth,
imgHeight,
canvasWidth,
canvasHeight,
}) => {
if (backgroundRepeat) {
// 将值转换成数组
let backgroundRepeatValueArr = getNumberValueFromStr(backgroundRepeat)
// 不处理
if (backgroundRepeatValueArr[0] === 'no-repeat' || (imgWidth >= canvasWidth && imgHeight >= canvasHeight)) {
return
}
}
}
繰り返し-x
次に、repeat-x
ペアのサポートを追加します.canvas
幅が画像の幅よりも大きい場合、横方向のタイルが描画され、描画はメソッドを繰り返し呼び出すため、メソッドにとパラメーターdrawImage
を渡す必要があります.また、メソッド内で描画を行う場合は、元の描画メソッドを呼び出す必要はありません:ctx
image
handleBackgroundRepeat
handleBackgroundRepeat
// 模拟background-repeat
// 如果在handleBackgroundRepeat里进行了绘制,那么会返回true
let notNeedDraw = handleBackgroundRepeat({
ctx,
image,
...
})
if (!notNeedDraw) {
drawImage(ctx, image, drawOpt)
}
// 根据参数绘制图片
const drawImage = (ctx, image, drawOpt) => {
ctx.drawImage(
image,
drawOpt.sx,
drawOpt.sy,
drawOpt.swidth,
drawOpt.sheight,
drawOpt.x,
drawOpt.y,
drawOpt.width,
drawOpt.height
)
}
描画メソッドは、再利用しやすいメソッドに抽出されます。
const handleBackgroundRepeat = ({
}) => {
// ...
// 水平平铺
if (backgroundRepeatValueArr[0] === 'repeat-x') {
if (canvasWidth > imgWidth) {
let x = 0
while (x < canvasWidth) {
drawImage(ctx, image, {
...drawOpt,
x
})
x += imgWidth
}
return true
}
}
// ...
}
幅を超えるまで、画像の配置位置x
パラメーターが更新されるたびに。canvas
繰り返し-y
正しいrepeat-y
処理は似ています:
const handleBackgroundRepeat = ({
}) => {
// ...
// 垂直平铺
if (backgroundRepeatValueArr[0] === 'repeat-y') {
if (canvasHeight > imgHeight) {
let y = 0
while (y < canvasHeight) {
drawImage(ctx, image, {
...drawOpt,
y
})
y += imgHeight
}
return true
}
}
// ...
}
繰り返す
そして最後にrepeat
、水平方向と垂直方向の両方で繰り返される値:
const handleBackgroundRepeat = ({
}) => {
// ...
// 平铺
if (backgroundRepeatValueArr[0] === 'repeat') {
let x = 0
while (x < canvasWidth) {
if (canvasHeight > imgHeight) {
let y = 0
while (y < canvasHeight) {
drawImage(ctx, image, {
...drawOpt,
x,
y
})
y += imgHeight
}
}
x += imgWidth
}
return true
}
}
左から右に、列ごとに、x
余分なcanvas
幅まで水平方向に、y
余分なcanvas
高さまで垂直方向に描画されます。
background-size、background-position との組み合わせ
最後に、最初の 2 つの属性との組み合わせを見てください。
css
スタイル:
.cssBox {
background-image: url('/4.png');
background-repeat: repeat;
background-size: 50%;
background-position: 50% 50%;
}
効果は次のとおりです。
画像のサイズは正しいが、位置が正しくない.css
これを行う最善の方法は、最初にbackground-position
の値に従って画像を配置し、次に並べて並べることですが、明らかにこの状況を無視して、0 0
その位置から描画を開始します毎回。
原理を知っていれば、解決策も非常に簡単です.handleBackgroundPosition
方法で計算されていますx、y
.つまり、タイリング前の最初の画像の配置位置:
x、y
左と上に何枚の画像を並べることができるかを計算し、次のサイクルの初期値として水平方向と垂直方向の最初の画像の位置を計算するだけです。
const handleBackgroundRepeat = ({
}) => {
// 保存在handleBackgroundPosition中计算出来的x、y
let ox = drawOpt.x
let oy = drawOpt.y
// 计算ox和oy能平铺的图片数量
let oxRepeatNum = Math.ceil(ox / imgWidth)
let oyRepeatNum = Math.ceil(oy / imgHeight)
// 计算ox和oy第一张图片的位置
let oxRepeatX = ox - oxRepeatNum * imgWidth
let oxRepeatY = oy - oyRepeatNum * imgHeight
// 将oxRepeatX和oxRepeatY作为后续循环的x、y的初始值
// ...
// 平铺
if (backgroundRepeatValueArr[0] === 'repeat') {
let x = oxRepeatX
while (x < canvasWidth) {
if (canvasHeight > imgHeight) {
let y = oxRepeatY
// ...
}
}
}
}
終わり
この記事では、、 、およびcanvas
でシミュレートされた3 つの属性の効果の一部を単純に理解しています。完全なソース コードはhttps://github.com/wanglin2/simulateCSSBackgroundInCanvascss
にあります。background-size
background-position
background-repeat