Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何在canvas中模拟css的背景图片样式

如何在canvas中模拟css的背景图片样式

作者头像
街角小林
发布于 2023-03-01 06:24:22
发布于 2023-03-01 06:24:22
7.5K00
代码可运行
举报
运行总次数:0
代码可运行

笔者开源了一个Web思维导图mind-map,最近在优化背景图片效果的时候遇到了一个问题,页面上展示时背景图片是通过css使用background-image渲染的,而导出的时候实际上是绘制到canvas上导出的,那么就会有个问题,css的背景图片支持比较丰富的效果,比如通过background-size设置大小,通过background-position设置位置,通过background-repeat设置重复,但是canvas笔者只找到一个createPattern()方法,且只支持设置重复效果,那么如何在canvas里模拟一定的css背景效果呢,不要走开,接下来一起来试试。

首先要说明的是不会去完美完整100%模拟css的所有效果,因为css太强大了,属性值组合很灵活,且种类非常多,其中单位就很多种,所有只会模拟一些常见的情况,单位也只考虑px%

读完本文,你还可以顺便复习一下canvasdrawImage方法,以及css背景设置的几个属性的用法。

canvas的drawImage()方法

总的来说,我们会使用canvasdrawImage()方法来绘制背景图片,先来大致看一下这个方法,这个方法接收的参数比较多:

只有三个参数是必填的。

基本框架和工具方法

核心逻辑就是加载图片,然后使用drawImage方法绘制图片,无非是根据各种css的属性和值来计算drawImage的参数,所以可以写出下面的函数基本框架:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)
  }
}

接下来看几个工具函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 将以空格分隔的字符串值转换成成数字/单位/值数组
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']形式。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 缩放宽度
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 属性用于设置背景图片的大小,可以接受四种类型的值,依次来模拟一下。

length类型

设置背景图片的高度和宽度。第一个值设置宽度,第二个值设置高度。如果只给出一个值,第二个默认为 auto(自动)。

css样式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.cssBox {
    background-image: url('/1.jpg');
    background-repeat: no-repeat;
    background-size: 300px;
}

只设置一个值,那么代表背景图片显示的实际宽度,高度没有设置,那么会根据图片的长宽比自动缩放,效果如下:

canvas中模拟很简单,需要传给drawImage方法四个参数:img、x、y、width、heightimg代表图片,x、y代表在画布上放置图片的位置,没有特殊设置,显然就是0、0width、height代表将图片缩放到指定大小,如果background-size只传了一个值,那么width直接设置成这个值,而height则根据图片的长宽比进行计算,如果传了两个值,那么分别把两个值传给width、height即可,另外需要对值为auto的进行一下处理,实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)
      }
    }
}

效果如下:

设置两个值的效果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
background-size: 300px 400px;

percentage类型

将计算相对于背景定位区域的百分比。第一个值设置宽度百分比,第二个值设置的高度百分比。如果只给出一个值,第二个默认为auto(自动)。比如设置了50% 80%,意思是将图片缩放到背景区域的50%宽度和80%高度。

css样式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.cssBox {
    background-image: url('/1.jpg');
    background-repeat: no-repeat;
    background-size: 50% 80%;
}

实现也很简单,在前面的基础上判断一下单位是否是%,是的话就按照canvas的宽高来计算图片要显示的宽高,第二值没有设置或者为auto,跟之前一样也是根据图片的宽高比来自适应。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)
    }
  }
}

效果如下:

cover类型

background-size设置为cover代表图片会保持原来的宽高比,并且缩放成将完全覆盖背景定位区域的最小大小,注意,图片不会变形。

css样式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.cssBox {
    background-image: url('/3.jpeg');
    background-repeat: no-repeat;
    background-size: cover;
}

这个实现也很简单,根据图片的宽高比和canvas的宽高比判断,到底是缩放图片的宽度和canvas的宽度一致,还是缩放图片的高度和canvas的高度一致。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
    }
    // ...
}

效果如下:

contain类型

background-size设置为contain类型表示图片还是会保持原有的宽高比,并且缩放成适合背景定位区域的最大大小,也就是图片会显示完整,但是不一定会铺满背景的水平和垂直两个方向,在某个方向可能会有留白。

css样式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.cssBox {
    background-image: url('/1.jpg');
    background-repeat: no-repeat;
    background-size: contain;
}

实现刚好和cover类型的实现反过来即可,如果图片的宽高比大于canvas的宽高比,为了让图片显示完全,让图片的宽度和canvas的宽度一致,高度自适应。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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%,它也支持几种不同类型的值,一一来看。

percentage类型

第一个值设置水平位置,第二个值设置垂直位置。左上角是0%0%,右下角是100%100%,如果只设置了一个值,第二个默认为50%,比如设置为50% 60%,意思是将图片的50% 60%位置和背景区域的50% 60%位置进行对齐,又比如50% 50%,代表图片中心点和背景区域中心点重合。

css样式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.cssBox {
    background-image: url('/2.jpg');
    background-repeat: no-repeat;
    background-position: 50% 50%;
}

实现上我们只需要用到drawImage方法的imgx、y三个参数,图片的宽高不会进行缩放,根据比例分别算出在canvas和图片上对应的距离,他们的差值即为图片在canvas上显示的位置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
      }
    }
  }
}

效果如下:

length类型

第一个值代表水平位置,第二个值代表垂直位置。左上角是0 0。单位可以是px或任何其他css单位,当然,我们只考虑px。如果仅指定了一个值,其他值将是50%。所以你可以混合使用%px

css样式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.cssBox {
    background-image: url('/2.jpg');
    background-repeat: no-repeat;
    background-position: 50px 150px;
}

这个实现更简单,直接把值传给drawImagex、y参数即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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]
    }
}

关键词类型

也就是通过lefttop之类的关键词进行组合,比如:left topcenter centercenter bottom等。可以看做是特殊的%值,所以我们只要写一个映射将这些关键词对应上百分比数值即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.cssBox {
    background-image: url('/2.jpg');
    background-repeat: no-repeat;
    background-position: right bottom;
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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组合使用会发生什么情况。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.cssBox {
    background-image: url('/2.jpg');
    background-repeat: no-repeat;
    background-size: cover;
    background-position: right bottom;
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
drawBackgroundImageToCanvas(ctx, width, height, this.img, {
    backgroundSize: 'cover',
    backgroundPosition: 'right bottom'
})

结果如下:

不一致,这是为啥呢,我们来梳理一下,首先在处理background-size会计算出drawImage参数中的width、height,也就是图片在canvas中显示的宽高,而在处理background-position时会用到图片的宽高,但是我们传的还是图片的原始宽高,这样计算出来当然是有问题的,修改一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 模拟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-repeatbackground-image不会重复

接下来我们实现一下这几种情况。

no-repeat

首先判断图片的宽高是否都比背景区域大,是的话就不需要平铺,也就不用处理,另外值为no-repeat也不需要做处理:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 模拟background-repeat
handleBackgroundRepeat({
    backgroundRepeat,
    drawOpt,
    imgWidth: drawOpt.width,
    imgHeight: drawOpt.height,
    imageRatio,
    canvasWidth: width,
    canvasHeight: height,
    canvasRatio
})

可以看到这里我们传的图片的宽高也是经background-size计算后的图片显示宽高。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 模拟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
        }
    }
}

repeat-x

接下来增加对repeat-x的支持,当canvas的宽度大于图片的宽度,那么水平平铺进行绘制,绘制会重复调用drawImage方法,所以还需要再传递ctximage参数给handleBackgroundRepeat方法,另外如果handleBackgroundRepeat方法里进行了绘制,原来的绘制方法就不用再调用了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 模拟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
  )
}

将绘制的方法提取成了一个方法,方便复用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的宽度。

repeat-y

repeat-y的处理也是类似的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

最后就是repeat值,也就是水平和垂直都进行重复:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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组合

最后同样看一下和前两个属性的组合情况。

css样式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.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的初始值即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中模拟cssbackground-sizebackground-positionbackground-repeat三个属性的部分效果,完整源码在https://github.com/wanglin2/simulateCSSBackgroundInCanvas

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
常见开源分布式文件系统架构对比
文件系统是计算机中一个非常重要的组件,为存储设备提供一致的访问和管理方式。在不同的操作系统中,文件系统会有一些差别,但也有一些共性几十年都没怎么变化:
Juicedata
2021/12/10
1.3K0
常见开源分布式文件系统架构对比
浅析 GlusterFS 与 JuiceFS 的架构异同
在进行分布式文件存储解决方案的选型时,GlusterFS 无疑是一个不可忽视的考虑对象。作为一款开源的软件定义分布式存储解决方案,GlusterFS 能够在单个集群中支持高达 PiB 级别的数据存储。自从首次发布以来,已经有超过十年的发展历程。目前,该项目主要由 Red Hat 负责维护,并且在全球范围内拥有庞大的用户群体。本文旨在通过对比分析的方式,介绍 GlusterFS 与 JuiceFS 的区别,为您的团队在技术选型过程中提供一些参考。
Juicedata
2023/08/26
5120
浅析 GlusterFS 与 JuiceFS 的架构异同
大规模分布式存储系统原理解析与架构实战
1.分布式存储系统是大量普通 PC服务器通过Internet互联,对外作为一个整体提供存储服务
硬核项目经理
2019/08/06
2.3K0
分布式文件系统:JuiceFS 技术比对
Alluxio(/əˈlʌksio/)是大数据和机器学习生态系统中的数据访问层。最初作为研究项目「Tachyon」,它是在加州大学伯克利分校的 AMPLab 作为创建者 2013 年的博士论文创建的。Alluxio 于 2014 年开源。
Freedom123
2024/03/29
1.2K0
分布式文件系统:JuiceFS 技术比对
灵活地横向扩展:从文件系统到分布式文件系统
👆点击“博文视点Broadview”,获取更多书讯 我们无时无刻不在使用文件系统,进行开发时在使用文件系统,浏览网页时在使用文件系统,玩手机时也在使用文件系统。 对于非专业人士来说,可能根本不知道文件系统为何物。因为,通常来说,我们在使用文件系统时一般不会感知到文件系统的存在。即使是程序开发人员,很多人对文件系统也是一知半解。 虽然文件系统经常不被感知,但是文件系统是非常重要的。在 Linux 中,文件系统是其内核的四大子系统之一;微软的 DOS(Disk Operating System,磁盘管理系统
博文视点Broadview
2022/03/03
3650
分布式文件系统:JuiceFS 技术架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90ZtG0tw-1687771442157)(https://juicefs.com/docs/zh/assets/images/juicefs-arch-new-ab6339cb1408945cc9b70dc091c523c5.png)]
Freedom123
2024/03/29
8450
分布式文件系统:JuiceFS 技术架构
谷歌Colossus文件系统的设计经验
Colossus,巨人,谷歌第二代GFS文件系统。与GFS相比,Colossus相关的文章和信息却零星稀少。
用户9624935
2022/04/02
2K0
谷歌Colossus文件系统的设计经验
分布式(集群)文件系统的设计
本文讲的分布式文件系统,是通过集群来实现的,因此也是集群文件系统。本文介绍下分布式文件系统中的常见问题及GFS中给出的解决方法。
全栈程序员站长
2022/07/12
5660
如何使用 etcd 实现分布式 /etc 目录
etcd 是一款兼具一致性和高可用性的键值数据库,简单、安全、快速、可信,目前是 Kubernetes 的首要数据存储。我们先来看一段 etcd 官方对于名字的解释。
Juicedata
2022/06/27
6180
如何使用 etcd 实现分布式 /etc 目录
GFS的分布式哲学:HDFS的一致性成就,归功于我的失败……
陈东明,具有丰富的大规模系统构建和基础架构的研发经验,善于复杂业务需求下的大并发、分布式系统设计和持续优化。近年专注于分布式系统一致性的研究,常年坚持技术文章创作和社区分享。曾就职于饿了么、百度,主导开发饿了么key-value数据库,负责百度即时通讯产品的架构设计。个人微信公众号dongming_cdm。本文是本人新书《分布式系统与一致性》的一个章节,节选出来和大家分享、讨论。
jeanron100
2021/07/15
1.4K0
GFS的分布式哲学:HDFS的一致性成就,归功于我的失败……
分布式文件系统.get(V2)No.106
2018年9月28号,我估计会记得很久这一天,因为那天刚刚好是我来西厂的一周年,那天刚刚好是农历生日,刚刚好那天晚上我挖了一个大坑,跟遣怀师兄和小美姐姐一起填坑到深夜,真是难忘的一天。。。。。
大蕉
2018/10/26
4920
使用 Go 打造百亿级文件系统的实践之旅
JuiceFS 企业版是一款为云环境设计的分布式文件系统,单命名空间内可稳定管理高达百亿级数量的文件。
深度学习与Python
2024/02/17
2750
使用 Go 打造百亿级文件系统的实践之旅
AI训练存储方案选谁?DeepSeek 3FS与JuiceFS的全面对比
近期,DeepSeek 开源了其文件系统 Fire-Flyer File System (3FS),这一举措让文件系统这一已有70多年历史的技术再次成为焦点。在AI领域,企业需要处理大量非结构化数据,如文本、图像和视频,同时面临数据量的急剧增长,分布式文件系统因此成为AI训练中不可或缺的存储技术。
福大大架构师每日一题
2025/03/24
4180
AI训练存储方案选谁?DeepSeek 3FS与JuiceFS的全面对比
浅析 SeaweedFS 与 JuiceFS 架构异同
SeaweedFS 是一款高效的分布式文件存储系统,最早的设计原型参考了 Facebook 的 Haystack,具有快速读写小数据块的能力。本文将通过对比 SeaweedFS 与 JuiceFS 在设计与功能上的差异,以帮助读者进行更适合自己的选择。
Juicedata
2023/03/08
1.7K0
浅析 SeaweedFS 与 JuiceFS 架构异同
分布式文件系统:JuiceFS 简介
JuiceFS 是一款面向云原生设计的高性能分布式文件系统,在 Apache 2.0 开源协议下发布。提供完备的 POSIX 兼容性,可将几乎所有对象存储接入本地作为海量本地磁盘使用,亦可同时在跨平台、跨地区的不同主机上挂载读写。
Freedom123
2024/03/29
3870
分布式文件系统设计,该从哪些方面考虑?
分布式文件系统是分布式领域的一个基础应用,其中最著名的毫无疑问是 HDFS/GFS。如今该领域已经趋向于成熟,但了解它的设计要点和思想,对我们将来面临类似场景 / 问题时,具有借鉴意义。并且,分布式文件系统并非只有 HDFS/GFS 这一种形态,在它之外,还有其他形态各异、各有千秋的产品形态,对它们的了解,也对扩展我们的视野有所俾益。本文试图分析和思考,在分布式文件系统领域,我们要解决哪些问题、有些什么样的方案、以及各自的选择依据。
芋道源码
2020/06/16
2.3K0
分布式文件系统设计,该从哪些方面考虑?
如何借助分布式存储 JuiceFS 加速 AI 模型训练
传统的机器学习模型,数据集比较小,模型的算法也比较简单,使用单机存储,或者本地硬盘就足够了,像 JuiceFS 这样的分布式存储并不是必需品。
Juicedata
2023/05/01
8080
如何借助分布式存储 JuiceFS 加速 AI 模型训练
分布式文件系统—Google File System介绍
我们知道如要要从磁盘取数据,需要告诉控制器从哪取,取多长等信息,如果这步由应用来做,那实在太麻烦。所以操作系统提供了一个中间层,它管理本地的磁盘存储资源、提供文件到存储位置的映射,并抽象出一套文件访问接口供用户使用。对用户来说只需记住文件名和路径,其他的与磁盘块打交道的事就交给这个中间层来做,这个中间层即为文件系统。
MySQL轻松学
2019/11/12
2.2K0
分布式文件系统—Google File System介绍
MFS分布式文件系统
一、工作原理 1、分布式原理 分布式文件系统就是把一些分散在多台计算机上的共享文件夹,集合到一个共享文件夹内,用户要访问这些文件夹的时候,只要打开一个文件夹,就可以的看到所有链接到此文件夹内的共享文件夹。 2、MFS原理 MFS是一个具有容错性的网络分布式文件系统,它把数据分散存放在多个物理服务器上,而呈现给用户的则是一个统一的资源。 1)MFS的组成 元数据服务器(Master):在整个体系中负责管理文件系统,维护元数据,目前不支持高可用。 元数据日志服务器(MetaLogger):备份Master服务器
L宝宝聊IT
2018/06/20
1.4K0
分布式文件系统监控
分布式文件系统用来存储各种非结构化数据,例如海量的图片,海量的视频,海量的xml等数据。在这种分布式存储中,是不支持随机的读写的,要么直接覆盖,要么删除然后再修改。
SRE运维实践
2019/07/08
1.1K0
推荐阅读
相关推荐
常见开源分布式文件系统架构对比
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档