Cómo simular el estilo de imagen de fondo de css en canvas

El autor ha creado un mapa mental web de código abierto . Recientemente, encontré un problema al optimizar el efecto de la imagen de fondo. Cuando se muestra en la página, la imagen de fondo se representa cssusando , pero cuando se exporta, en realidad está dibujado en la parte superior Entonces habrá un problema, la imagen de fondo de la imagen de fondo admite efectos más ricos, como configurar el tamaño, la posición y la repetición, pero el autor solo encontró un método y solo admite configurar la repetición efecto, así que cómo simular un cierto En cuanto al efecto de fondo, no se vaya, intentémoslo juntos a continuación.background-imagecanvascssbackground-sizebackground-positionbackground-repeatcanvascreatePattern()canvascss

Lo primero que hay que explicar es que no simulará 100%todos csslos efectos de forma perfecta y completa, porque es cssdemasiado potente, la combinación de valores de los atributos es muy flexible, y hay muchos tipos, entre los que hay muchos tipos de unidades, por lo que solo se simularán algunas situaciones comunes y solo se considerarán pxunidades %.

Después de leer este artículo, también puede revisar el canvasmétodo drawImagey cssel uso de varios atributos establecidos en segundo plano.

El método drawImage() de canvas

En general, usaremos canvasel drawImage()método para dibujar la imagen de fondo. Echemos un vistazo a este método primero. Este método recibe más parámetros:

Solo se requieren tres parámetros.

Marco básico y herramientas.

La lógica central es cargar la imagen y luego usar drawImageel método para dibujar la imagen, que no es más que parámetros csscalculados de acuerdo con varios atributos y valores drawImage, por lo que se puede escribir el siguiente marco básico de la función:

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)
  }
}

A continuación, veamos algunas funciones de herramientas.

// 将以空格分隔的字符串值转换成成数字/单位/值数组
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
    }
  })
}

cssEl valor del atributo es de tipo cadena o número, por ejemplo 100px 100% auto, no es conveniente usarlo directamente, por lo que se convierte en [[100, 'px'], [100, '%'], 'auto']un formulario.

// 缩放宽度
const zoomWidth = (ratio, height) => {
    
    
    // w / height = ratio
    return ratio * height
}

// 缩放高度
const zoomHeight = (ratio, width) => {
    
    
  // width / h = ratio
  return width / ratio
}

Calcule el ancho o alto escalado en función de la relación original y el nuevo ancho o alto.

Simular la propiedad de tamaño de fondo

background-repeatEl valor predeterminado es repeat, no consideramos el caso de duplicación, así que configúrelo en no-repeat.

background-sizeEl atributo se utiliza para establecer el tamaño de la imagen de fondo y puede aceptar cuatro tipos de valores, que se simulan a su vez.

tipo de longitud

Establezca la altura y el ancho de la imagen de fondo. El primer valor establece el ancho y el segundo establece la altura. Si solo se proporciona un valor, el segundo predeterminado es automático (automático).

cssEl estilo es el siguiente:

.cssBox {
    
    
    background-image: url('/1.jpg');
    background-repeat: no-repeat;
    background-size: 300px;
}

Si solo se establece un valor, representa el ancho real de la visualización de la imagen de fondo. Si no se establece la altura, se escalará automáticamente de acuerdo con la relación de aspecto de la imagen. El efecto es el siguiente:

La simulación es canvasmuy simple y se deben pasar cuatro parámetros al drawImagemétodo: img、x、y、width、height, imgrepresentando la imagen, x、yrepresentando la posición de colocar la imagen en el lienzo, no hay una configuración especial, obviamente, 0、0representa width、heightacercar la imagen al especificado tamaño, si background-sizesolo se pasa un valor, luego widthconfigúrelo directamente en este valor y heightcalcule de acuerdo con la relación de aspecto de la imagen.Si se pasan dos valores, pase los dos valores por separado.Además width、height, necesita para autoprocesar el valor, de la siguiente manera:

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)
      }
    }
}

El efecto es el siguiente:

El efecto de establecer dos valores:

background-size: 300px 400px;

tipo de porcentaje

Se calculará el porcentaje del área localizada en relación con el fondo. El primer valor establece el porcentaje de ancho, el segundo valor establece el porcentaje de altura. Si solo se proporciona un valor, el segundo predeterminado es automático (automático). Por ejemplo, si está configurado 50% 80%, significa que la imagen se escalará al 50%ancho y 80%alto del área de fondo.

cssEl estilo es el siguiente:

.cssBox {
    
    
    background-image: url('/1.jpg');
    background-repeat: no-repeat;
    background-size: 50% 80%;
}

La implementación también es muy simple. Sobre la base de lo anterior, juzgue si la unidad es %, en caso afirmativo, canvascalcule el ancho y el alto de la imagen que se mostrará de acuerdo con el ancho y el alto de la imagen. El segundo valor no está configurado. o es, autocomo antes, también se basa en la relación de aspecto de la imagen para adaptar.

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)
    }
  }
}

El efecto es el siguiente:

tipo de cubierta

background-sizeConfigurado para significar que coverla imagen mantendrá su relación de aspecto original y escalado al tamaño mínimo que cubrirá completamente el área de posicionamiento del fondo. Tenga en cuenta que la imagen no se deformará.

cssEl estilo es el siguiente:

.cssBox {
    
    
    background-image: url('/3.jpeg');
    background-repeat: no-repeat;
    background-size: cover;
}

Esta implementación también es muy simple.De acuerdo con la relación de aspecto de la imagen y la canvasrelación de aspecto de la imagen, ya sea que el ancho de la imagen ampliada sea canvasel mismo que el ancho de la imagen, o que la altura de la imagen canvassea la misma que la altura de la imagen.

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
    }
    // ...
}

El efecto es el siguiente:

contener tipo

background-sizeConfigurarlo en containtipo significa que la imagen aún mantendrá la relación de aspecto original y se escalará al tamaño máximo adecuado para el área de posicionamiento del fondo, es decir, la imagen se mostrará por completo, pero no necesariamente cubrirá el fondo. horizontal y verticalmente Puede haber espacios en blanco en una dirección.

cssestilo:

.cssBox {
    
    
    background-image: url('/1.jpg');
    background-repeat: no-repeat;
    background-size: contain;
}

La implementación coveres justo lo contrario de la implementación del tipo. Si la relación de aspecto de la imagen es mayor que la canvasrelación de aspecto de la imagen, para que la imagen se muestre completamente, el ancho de la imagen canvases consistente con el ancho de la imagen, y la altura es autoadaptativa.

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
    }
}

El efecto es el siguiente:

background-sizeLa simulación aquí ha terminado, echemos un vistazo background-position.

Simular la propiedad de posición de fondo

Primero mire background-sizela situación en la que no está configurado.

background-positionLa propiedad se usa para establecer la posición inicial de la imagen de fondo, el valor predeterminado es 0% 0%, también admite varios tipos diferentes de valores, véalos uno por uno.

tipo de porcentaje

El primer valor establece la posición horizontal y el segundo valor establece la posición vertical. La esquina superior izquierda es 0%0%, la esquina inferior derecha es 100%100%, si solo se establece un valor, el segundo valor predeterminado es 50%, por ejemplo, se establece en 50% 60%, lo que significa alinear 50% 60%la posición de la imagen con la posición del área de fondo , y para ejemplo , que representa el punto central de la imagen y el centro de los puntos del área de fondo coinciden.50% 60%50% 50%

cssestilo:

.cssBox {
    
    
    background-image: url('/2.jpg');
    background-repeat: no-repeat;
    background-position: 50% 50%;
}

En términos de implementación, solo necesitamos usar los tres parámetros del drawImagemétodo . El ancho y el alto de la imagen no se escalarán, y la distancia correspondiente a la imagen se calcula de acuerdo con la proporción . Su diferencia es la posición de la imagen mostrada en la imagen.imgx、ycanvascanvas

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
      }
    }
  }
}

El efecto es el siguiente:

tipo de longitud

El primer valor representa la posición horizontal y el segundo valor representa la posición vertical. La esquina superior izquierda es 0 0. La unidad puede ser pxo cualquier otra cssunidad, por supuesto, solo consideramos px. Si solo se especifica un valor, los demás serán 50%. Así que puedes mezclar %y combinar px.

cssestilo:

.cssBox {
    
    
    background-image: url('/2.jpg');
    background-repeat: no-repeat;
    background-position: 50px 150px;
}

Esta implementación es más simple, simplemente pase el valor directamente al drawImageparámetro 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]
    }
}

tipo de palabra clave

Eso es combinar palabras clave como , left, y así sucesivamente. Se puede considerar como un valor especial, por lo que solo necesitamos escribir una asignación para asignar estas palabras clave a valores porcentuales.topleft topcenter centercenter 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
    })
    // ...
}

Combinado con tamaño de fondo

Finalmente, observamos background-sizelo que sucede cuando se combina con y .

.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'
})

El resultado es el siguiente:

Incoherente, ¿por qué es esto? Vamos a resolverlo. Primero, el procesamiento background-sizecalculará drawImagelos parámetros width、height, es decir, canvasel ancho y el alto de la imagen que se muestra en él, y background-positionel ancho y el alto de la imagen se usarán en el procesamiento, pero lo que pasamos sigue siendo la imagen El ancho y alto original de , por supuesto hay un problema con este cálculo, modifíquelo:

// 模拟background-position
handleBackgroundPosition({
    
    
    backgroundPosition,
    drawOpt,
    imgWidth: drawOpt.width,// 改为传计算后的图片的显示宽高
    imgHeight: drawOpt.height,
    imageRatio,
    canvasWidth: width,
    canvasHeight: height,
    canvasRatio
})

Ahora mira el efecto de nuevo:

Simular la propiedad de repetición de fondo

background-repeatEl atributo se usa para establecer cómo colocar el objeto en mosaico background-image. El valor predeterminado repeates, es decir, cuando la imagen es más pequeña que el área de fondo, se repetirá vertical y horizontalmente de forma predeterminada. Hay varios valores opcionales:

  • repeat-x: solo la posición horizontal repetirá la imagen de fondo
  • repeat-y: Solo la posición vertical repetirá la imagen de fondo
  • no-repeat: background-imageno repetiré

A continuación, implementamos estas situaciones.

sin repetición

En primer lugar, juzgue si el ancho y el alto de la imagen son más grandes que el área de fondo. Si es así, no hay necesidad de mosaico o procesamiento, y el otro valor no necesita no-repeatser procesado:

// 模拟background-repeat
handleBackgroundRepeat({
    
    
    backgroundRepeat,
    drawOpt,
    imgWidth: drawOpt.width,
    imgHeight: drawOpt.height,
    imageRatio,
    canvasWidth: width,
    canvasHeight: height,
    canvasRatio
})

Puede ver que el ancho y el alto de la imagen que cargamos aquí también es el background-sizeancho y el alto calculados para mostrar la imagen.

// 模拟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
        }
    }
}

repetir-x

A continuación, agregue repeat-xsoporte para el par. Cuando canvasel ancho sea mayor que el ancho de la imagen, se dibujará el mosaico horizontal y el dibujo llamará al drawImagemétodo repetidamente, por lo que es necesario pasar ctxy imageparámetros al handleBackgroundRepeatmétodo. Además, si handleBackgroundRepeatel dibujo se realiza en el método, el método de dibujo original No es necesario llamar:

// 模拟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
  )
}

El método de dibujo se extrae en un método para facilitar su reutilización.

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
      }
    }
    // ...
}

Cada vez que xse actualiza el parámetro de posición de ubicación de la imagen hasta que canvasse excede el ancho.

repetir-y

El manejo correcto repeat-yes similar:

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
      }
    }
    // ...
}

repetir

Y por último repeatel valor, que se repite tanto en horizontal como en vertical:

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
    }
}

De izquierda a derecha, se dibuja columna por columna, horizontalmente hasta el ancho xsobrante canvasy verticalmente hasta el alto ysobrante .canvas

Combinación con tamaño de fondo, posición de fondo

Finalmente, mira la combinación con los dos primeros atributos.

cssestilo:

.cssBox {
    
    
    background-image: url('/4.png');
    background-repeat: repeat;
    background-size: 50%;
    background-position: 50% 50%;
}

El efecto es el siguiente:

El tamaño de la imagen es correcto, pero la posición es incorrecta. La cssmejor manera de hacerlo es background-positioncolocar primero una imagen de acuerdo con el valor de y luego colocarla en mosaico, pero obviamente ignoramos esta situación y 0 0comenzamos a dibujar desde la posición. cada vez.

Conociendo el principio, la solución también es muy sencilla, se handleBackgroundPositionha calculado en el método x、y, es decir, la posición de colocación del primer cuadro antes del mosaico:

Solo necesitamos calcular cuántas imágenes se pueden colocar en mosaico a la izquierda y en la parte superior, y calcular la posición de la primera imagen en las direcciones horizontal y vertical como el x、yvalor inicial del ciclo siguiente.

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
          // ...
        }
      }
    }
}

fin

Este artículo simplemente se da cuenta de algunos de los efectos de los tres atributos canvassimulados en , y El código fuente completo cssestá en https://github.com/wanglin2/simulateCSSBackgroundInCanvas .background-sizebackground-positionbackground-repeat

Supongo que te gusta

Origin blog.csdn.net/sinat_33488770/article/details/129253709
Recomendado
Clasificación