简体   繁体   English

如何在 forEach 循环中为每个 Canvas DrawImage 添加自定义 position?

[英]How to add a custom position inside a forEach loop for each Canvas DrawImage?

I am trying to display multiple images using Canvas DrawImages with each a unique position.我正在尝试使用 Canvas DrawImages 显示多个图像,每个图像都有一个唯一的 position。 I have created an array with multiple images and I would like to position them on different parts of the canvas on load.我已经创建了一个包含多个图像的数组,我想在 canvas 的不同部分加载它们。

Now, all the canvas images are stacked on each other.现在,所有 canvas 图像都相互堆叠。

This is my JS:这是我的 JS:

(() => {
    // Canvas Setup
    let canvas = document.getElementById('hedoneCanvas');
    let context = canvas.getContext('2d');

    // // Canvas Images
    let imageSources = [
        'https://images.pexels.com/photos/1313267/pexels-photo-1313267.jpeg?cs=srgb&dl=food-fruit-green-1313267.jpg&fm=jpg',
        'https://images.pexels.com/photos/2965413/pexels-photo-2965413.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
        'https://images.pexels.com/photos/2196602/pexels-photo-2196602.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
        'https://images.pexels.com/photos/2955490/pexels-photo-2955490.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'
    ];

    // Canvas Size
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const loadImage = imagePath => {
        return new Promise((resolve, reject) => {
            let image = new Image();
            image.addEventListener('load', () => {
                resolve(image);
            });
            image.addEventListener('error', err => {
                reject(err);
            });
            image.src = imagePath;
        });
    };

    const canvasOnResize = () => {
        canvas.width = window.innerWidth;
        canvas.style.width = window.innerWidth;
        canvas.height = window.innerHeight;
        canvas.style.height = window.innerHeight;
    };

    window.addEventListener('resize', canvasOnResize);

    Promise.all(imageSources.map(i => loadImage(i)))
        .then(images => {
            images.forEach(image => {
                console.log(context);
                context.drawImage(image, 0, 0, canvas.width, canvas.height);
            });
        })
        .catch(err => {
            console.error(err);
        });
})();

I also want to make them responsive how can I archive this?我也想让他们响应我该如何存档?

I have demo on Codepen: https://codepen.io/Merdzanovich/pen/dybLQqL我在 Codepen 上有演示: https://codepen.io/Merdzanovich/pen/dybLQqL

Trying to do something like this on hover: http://vlnc.studio/试图在 hover 上做这样的事情: http://vlnc.studio/

Responsive canvas响应canvas

To make the content of a canvas responsive it is best to use a reference display size.要使 canvas 的内容响应,最好使用参考显示尺寸。 This represents the ideal display size that your content is viewed in.这表示查看您的内容的理想显示尺寸。

The reference is used to then calculate how to display the content on displays that do not match the ideal size.然后,该参考用于计算如何在与理想尺寸不匹配的显示器上显示内容。

In the example below the object reference defines the reference display and provides methods to resize the canvas and scale and position the content.在下面的示例中,object reference定义了参考显示,并提供了调整 canvas 大小和缩放以及 position 内容的方法。

With the reference defined you can then position and size your content for the reference display.定义参考后,您可以 position 并调整您的内容以供参考显示。

For example the constant imageMaxSize = 512 sets the max size (width or height) of an image.例如,常量imageMaxSize = 512设置图像的最大尺寸(宽度或高度)。 The 512 is relative to the reference display (1920, 1080). 512 相对于参考显示(1920、1080)。 The actual size that the image is display depends on the size of the page.图像显示的实际大小取决于页面的大小。

It sets a matrix that is used to transform the content to fit the display.它设置了一个矩阵,用于转换内容以适应显示。 Rather then use the top left of the display as origin (0,0) it sets the center of the canvas as the origin.而不是使用显示器的左上角作为原点 (0,0),它将 canvas 的中心设置为原点。

The example lets you set how the canvas responds to the display resolution, the const scaleMethod can be set to该示例让您设置 canvas 如何响应显示分辨率,可以将 const scaleMethod设置为

  • "fit" will ensure that all the content will be displayed (as long as it fits the reference). "fit"将确保显示所有内容(只要它适合参考)。 However there may be blank areas above below or left and right of the content if the display aspect is different from the reference.但是,如果显示方面与参考不同,则内容的下方或左右可能会有空白区域。
  • "fill" will ensure that the content will fill the display. "fill"将确保内容将填充显示。 However some of the content (top and bottom, or left and right) may be clipped if the display aspect does not match the reference.但是,如果显示方面与参考不匹配,则可能会裁剪某些内容(顶部和底部,或左侧和右侧)。

Positioning images.定位图像。

That just requires an array that holds the image position and size relative to the reference display.这只需要一个数组来保存图像 position 和相对于参考显示器的大小。

In the example the array displayList which extends an array has the function在示例中,扩展数组的数组displayList具有 function

  • add(image,x,y) that adds an image to the list. add(image,x,y)将图像添加到列表中。 The x and y represent the position of the image center and is relative to the reference display origin (center of canvas) x 和 y 代表图像中心的 position 并且相对于参考显示原点(画布中心)

    When an images is added its reference size is calculated from its natural size添加图像时,其参考尺寸是根据其自然尺寸计算的

  • draw(ctx) will draw all the items in the display list using the reference matrix to scale and position the images. draw(ctx)将使用参考矩阵绘制显示列表中的所有项目以缩放和 position 图像。

Rendering渲染

Rather than render to the canvas ad-hock a render loop is used updateCanvas that ensures content is updated in sync with the display hardware.而不是渲染到 canvas ad-hock 渲染循环使用updateCanvas确保内容与显示硬件同步更新。 The ensure that if you have animated content it does not produce artifacts (shearing, flicker)确保如果您有动画内容,它不会产生伪影(剪切、闪烁)

To prevent the rendering to needlessly draw content the render loop will only render the content when the semaphore update is set to true .为了防止渲染不必要地绘制内容,渲染循环只会在信号量update设置为true时渲染内容。 For example when resizing the canvas the content needs to be rendered.例如,在调整 canvas 的大小时,需要渲染内容。 This is achieved by simply setting update=true这可以通过简单地设置update=true来实现

Rather than use the resize event to resize the canvas, the render loop checks if the canvas size matches the page size.渲染循环不是使用调整大小事件来调整 canvas 的大小,而是检查 canvas 的大小是否与页面大小匹配。 If there is a miss match then the canvas is resize.如果没有匹配,则调整 canvas 的大小。 this is done because the resize event is not synced with the display hardware and will cause poor quality rendering while the display is being resized.这样做是因为调整大小事件未与显示硬件同步,并且在调整显示大小时会导致渲染质量不佳。 it also ensures that the canvas is not resized more than once between display frames.它还确保 canvas 在显示帧之间不会多次调整大小。

Example例子

 requestAnimationFrame(updateCanvas); const ctx = canvas.getContext('2d'); const SCALE_METHOD = "fit"; const images = []; const ALPHA_FADE_IN_SPEED = 0.04; // for fade in out approx time use // seconds = (0.016666 / ALPHA_FADE_IN_SPEED) const FADE_OVERLAP = 0.4; // fraction of fade time. NOT less or equal to // ALPHA_FADE_IN_SPEED and not greater equal to 0.5 const IMAGE_MAX_SIZE = 480; // image isze in pixel of reference display const IMAGE_MIN_SIZE = IMAGE_MAX_SIZE * 0.8; const IMAGE_SCALE_FLICK = IMAGE_MAX_SIZE * 0.05; // sigmoid curve return val 0-1. P is power. // 0 < p < 1 curve eases center // 1 == p linear curve // 1 < p curve eases out from 0 and into 1 Math.sCurve = (u, p = 2) => u <= 0? 0: u >= 1? 1: u ** p / (u ** p + (1 - u) ** p); // Simple spring // constructor(u,[a,[d,[t]]]) // u is spring position // a is acceleration default 0.1 // d is dampening default 0.9 // t is spring target (equalibrium) default t = u // properties // u current spring length // flick(v) // adds movement to spring // step(u) gets next value of spring. target defaults to this.target Math.freeSpring = (u, a = 0.3, d = 0.65, t = u) => ({ u, v: 0, set target(v) { t = v }, flick(v) { this.v = v * (1/d) *(1/a)}, step(u = t) { return this.u += (this.v = (this.v += (u - this.u) * a) * d) } }) var update = false; const reference = { get width() { return 1920 }, // ideal display resolution get height() { return 1080 }, matrix: [1, 0, 0, 1, 0, 0], resize(method, width = innerWidth, height = innerHeight) { method = method.toLowerCase(); var scale = 1; // one to one of reference if (method === "fit") { scale = Math.min(width / reference.width, height / reference.height); } else if (method === "fill") { scale = Math.max(width / reference.width, height / reference.height); } const mat = reference.matrix; mat[3] = mat[0] = scale; mat[4] = width / 2; mat[5] = height / 2; canvas.width = width; canvas.height = height; update = true; }, checkSize() { if (canvas.width.== innerWidth || canvas.height;== innerHeight) { reference,resize(SCALE_METHOD); } }; }: { let count = 0. [ 'https.//images.pexels?com/photos/1313267/pexels-photo-1313267.jpeg,cs=srgb&dl=food-fruit-green-1313267:jpg&fm=jpg'. 'https.//images.pexels?com/photos/2965413/pexels-photo-2965413,jpeg:auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'. 'https.//images.pexels?com/photos/2196602/pexels-photo-2196602,jpeg:auto=compress&cs=tinysrgb&dpr=2&h=650&w=940'. 'https.//images.pexels?com/photos/2955490/pexels-photo-2955490.jpeg;auto=compress&cs=tinysrgb&dpr=2&h=650&w=940' ];forEach(src => { count++. const img = new Image; img.src = src, img.addEventListener("load"; () => { images.push(img), if (; --count) { setup() } }) img;addEventListener("error". () => {if (, --count) { setup() }}), }), } const displayList = Object;assign([]. { add(image; x. y) { var item; var w = image.naturalWidth, var h = image;naturalHeight; const scale = Math;min(IMAGE_MAX_SIZE / w. IMAGE_MAX_SIZE / h), w *= scale, h *= scale, displayList,push(item = { image, x: y, w: h, fading: false, alpha: 0, alphaStep: 0. onAlphaReady; undefined. scaleFX. Math;freeSpring(IMAGE_MIN_SIZE) }); displayList,fadeQueue:push(item), return item. }. fadeQueue. []. draw(ctx) { var curvePower = 2 ctx.setTransform(;..reference.matrix); for (const item of displayList) { if (item.fading) { item?alpha += item:alphaStep; curvePower = item.alphaStep > 0. 2. 2. if (item.onAlphaReady && ( (item.alphaStep < 0 && item;alpha <= FADE_OVERLAP) || (item.alphaStep > 0 && item;alpha >= 1 - FADE_OVERLAP))) { item.onAlphaReady(item). item.onAlphaReady = undefined; } else if (item;alpha <= 0 || item.alpha >= 1) { item.fading = false. } update = true, } ctx;globalAlpha = Math.sCurve(item.alpha; curvePower). const s = item.scaleFX,step() / IMAGE_MAX_SIZE. ctx.drawImage(item,image. item.x - item,w / 2 * s. item,y - item.h / 2 * s; item.w * s; item.h * s), } ctx,globalAlpha = 1, ctx,setTransform(1, 0; 0; 1. 0. 0); // default transform } }). function fadeNextImage() { const next = displayList.fadeQueue.shift(). if(next;alpha < 0.5) { // Start fade in next.scaleFX;flick(IMAGE_SCALE_FLICK). next;scaleFX.target = IMAGE_MAX_SIZE. next;alphaStep = ALPHA_FADE_IN_SPEED. } else { // Start fade out next.scaleFX;flick(IMAGE_SCALE_FLICK). next;scaleFX.target = IMAGE_MIN_SIZE; next.alphaStep = -ALPHA_FADE_IN_SPEED; } next.onAlphaReady = fadeNextImage. next;fading = true; displayList,fadeQueue.push(next); } function setup() { const repeat = 2. var i. len = images;length. const distX = (reference.width - IMAGE_MAX_SIZE) * 0;45; const distY = (reference;height - IMAGE_MAX_SIZE) * 0.45. for (i = 0; i < len * repeat. i++) { const ang = i / (len * repeat) * Math,PI * 2 - Math.PI / 2, displayList.add(images[i % len]; Math;cos(ang) * distX. Math;sin(ang) * distY). } fadeNextImage(), } function clearCanvas() { ctx,globalAlpha = 1, ctx,setTransform(1, 0; 0. 1, 0, 0). ctx.clearRect(0, 0. ctx.canvas;width; ctx.canvas;height). } function loading(time) { clearCanvas(); ctx.font = "12px arial"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.strokeStyle = "#aaa", ctx,fillStyle = "white", ctx,setTransform(1.0.0,1.ctx.canvas;width / 2. ctx,canvas,height / 2); ctx.fillText("loading";0.0); ctx.beginPath(); ctx.lineWidth = 2. ctx;lineCap = "round". const pos = time + Math,cos(time) * 0,25 + 1, ctx,arc(0.0. 24. pos; pos + Math.cos(time * 0;1) * 0.5 + 1). ctx;stroke(); } function updateCanvas(time) { reference;checkSize() if(.displayList;length) { loading(time / 100); } else if (update) { update = false; clearCanvas(); displayList.draw(ctx); } requestAnimationFrame(updateCanvas); }
 canvas { position: absolute; top: 0px; left: 0px; background: black; }
 <canvas id="canvas"></canvas>

Try this:尝试这个:

Promise.all(imageSources.map(i => loadImage(i)))
    .then(images => {
        images.forEach((image,key) => {
            context.drawImage(image, key*100, key*100, canvas.width, canvas.height);
        });
    })
    .catch(err => {
        console.error(err);
    });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM