TypeError: не удалось выполнить «drawImage» в «CanvasRenderingContext2D»: предоставленное значение не относится к типу (CSSImageValue или HTMLImageElement

Следуя туториалу с тем же точным кодом, но выводя эту ошибку на консоль при попытке нарисовать изображение на холсте:

SpriteSheet.js:30 Uncaught (in promise) TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)

Понятия не имею, почему это происходит? Первое изображение хорошо прорисовывается на холсте, но второе не отображается и получает эту ошибку. Использование обещаний, так что все изображения должны быть загружены перед их использованием, верно? Он отлично работает, если я изменю конец файла script.js на drawBackground(level.backgrounds[1], context, sprites);

script.js

import SpriteSheet from './SpriteSheet.js';
import {loadImage, loadLevel} from './loaders.js';

function drawBackground(background, context, sprites) {
    background.ranges.forEach(([x1, x2, y1, y2]) => {
        for (let x = x1; x < x2; ++x) {
            for (let y = y1; y < y2; ++y) {
                sprites.drawTile(background.tile, context, x, y);
            }
        }
    });
}

function loadBackgroundSprites() {
  return loadImage('SEA01.png').then(image => {
    const sprites = new SpriteSheet(image, 16, 16);
    sprites.define('ocean', 0, 0);
    return sprites;
});
  return loadImage('ground.png').then(image=> {
    const sprites = new SpriteSheet(image, 16, 16);
    sprites.define('ground', 12, 0);
  });
}

const canvas = document.getElementById('gamearea');
const context = canvas.getContext('2d');

Promise.all([
  loadBackgroundSprites(),
  loadLevel('1-1')
]).then(([sprites,level]) => {
  console.info(level);
  drawBackground(level.backgrounds[0], context, sprites);
});

loader.js

export function loadImage(url) {
    return new Promise(resolve => {
        const image = new Image();
        image.addEventListener('load', () => {
            resolve(image);
        });
        image.src = url;
    });
}

export function loadLevel(name) {
    return fetch(`/levels/${name}.json`)
    .then(r => r.json());
}

spritesheet.js

export default class SpriteSheet {
    constructor(image, w = 16, h = 16) {
        this.image = image;
        this.width = w;
        this.height = h;
        this.tiles = new Map();
    }

    define(name, x, y) {
        const buffer = document.createElement('canvas');
        buffer.height = this.height;
        buffer.width = this.width;
        buffer
            .getContext('2d')
            .drawImage(
                this.image,
                this.width * x,
                this.height * y,
                this.width,
                this.height,
                0,
                0,
                this.width,
                this.height);
        this.tiles.set(name, buffer);
    }

    draw(name, context, x, y) {
        const buffer = this.tiles.get(name);
        context.drawImage(buffer, x, y);
    }

    drawTile(name, context, x, y) {
        this.draw(name, context, x * this.width, y * this.height);
    }
}

1-1.json

{
    "backgrounds": [
        {
            "tile": "ocean",
            "ranges": [
                [
                    0, 50,
                    0, 25
                ]
            ]
        },
        {
            "tile": "ground",
            "ranges": [
                [
                    18, 25,
                    10, 15
                ]
            ]
        }
    ]
}

Заранее спасибо.

Обновлено:

Я обновил свой код, чтобы загружать изображения в разные функции, но получаю ту же ошибку:

script.js

import SpriteSheet from './SpriteSheet.js';
import {loadImage, loadLevel} from './loaders.js';

const canvas = document.getElementById('gamearea');
const context = canvas.getContext('2d');

function drawBackground(background, context, sprites) {
    background.ranges.forEach(([x1, x2, y1, y2]) => {
        for (let x = x1; x < x2; ++x) {
            for (let y = y1; y < y2; ++y) {
                sprites.drawTile(background.tile, context, x, y);
            }
        }
    });
}

function drawMario(background, context, mario) {
    background.ranges.forEach(([x1, x2, y1, y2]) => {
        for (let x = x1; x < x2; ++x) {
            for (let y = y1; y < y2; ++y) {
                mario.drawTile(background.tile, context, x, y);
            }
        }
    });
}

function loadBackgroundSprites() {
  return loadImage('SEA01.png').then(image => {
    const sprites = new SpriteSheet(image, 16, 16);
    sprites.define('ocean', 0, 0);
    return sprites;
  });
}

function loadDirtSprite() {
  return loadImage('ground.png').then(image => {
    const mario = new SpriteSheet(image, 16, 16);
    mario.define('ground', 12, 0);
    return mario;
  });
}



Promise.all([
  loadBackgroundSprites(),
  loadLevel('1-1'),
  loadDirtSprite()
]).then(([sprites, level, mario]) => {
  level.backgrounds.forEach(background => {
    drawBackground(background, context, sprites, mario);
  });
  mario.draw('ground', context, 1, 16);
});
function loadBackgroundSprites имеет два оператора возврата, второй из которых генерирует «недостижимый код» предупреждение (код для загрузки и создания «наземного» спрайта не выполняется). Это связано с проблемой? У вас есть другие ошибки консоли?
traktor 02.05.2018 04:22

Это определенно связано с тем, что упомянул @ traktor53: this.tiles.get('ground') вернет undefined, потому что loadImage('ground.png') никогда не будет вызван.

Kaiido 02.05.2018 09:49

@ traktor53 это вообще не связано, потому что, если я помещаю их в другую функцию и вызываю из своего обещания, я все равно получаю ту же ошибку. И нет, больше никаких ошибок, только эта. Я обновил свой OP другим кодом, но с той же ошибкой

Limpuls 02.05.2018 15:16
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
3
4 063
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

заменен ответ:

Ошибка №1.

Упомянутый в комментарии к вопросу, исходный код содержит ошибку в loadBackgroundSprites:

function loadBackgroundSprites() {
  return loadImage('SEA01.png').then(image => {
    const sprites = new SpriteSheet(image, 16, 16);
    sprites.define('ocean', 0, 0);
    return sprites;
});                                  // <-- bad indent
  return loadImage('ground.png').then(image=> {
    const sprites = new SpriteSheet(image, 16, 16);
    sprites.define('ground', 12, 0);
  });
}

Строка посередине, отмеченная как «плохой отступ», - это место, где заканчивается оператор return, начатый в начале loadBackgroundSprites, и выполнение возвращается вызывающему. Оператор return после строки «плохой отступ» никогда не выполняется и генерирует консольное сообщение.

⚠ unreachable code after return statement [Learn More]

Поскольку предупреждение не является фатальным, функция возвращается без ошибок.

Первое исправление этой ошибки - удалить недостижимый код. Требуются два дополнительных исправления, как описано в следующей ошибке.

  • измените загруженный URL-адрес на файл таблицы спрайтов (еще предстоит подготовить),
  • определите тайлы "океана" и "земли" в обработчике обещаний для loadImage

Ошибка №2.

Экземпляры класса SpriteSheet содержат одно изображение, переданное конструктору в качестве аргумента. Это изображение должно содержать массив прямоугольных плиток фиксированной ширины и высоты, соответствующие значениям 2-го и 3-го параметров, используемых в вызове конструктора. Это не то, что вы делаете.

Фактически, определение плитки в экземпляре SpriteSheet копирует отдельную плитку из изображения листа спрайтов в новый объект с именем Canvas для использования функциями рисования.

Предлагаемое средство для работы с листами спрайтов в том виде, в каком оно разработано, - создать фоновое изображение листа спрайтов, которое содержит как минимум два фрагмента 16x16 для океана и земли. Их положение в изображении листа спрайтов (считая плитки слева и сверху вниз) определяет, как определение плитки должно быть закодировано в loadBackgroundSprites.

Для этого решения удалите ссылки на код dirtSprite. И удалите ссылки на изображения на «SEA01.png» и «ground.png» - loadBackgroundSprites должен загружать URL для комбинированного листа спрайтов, содержащего обе плитки.

В цепочке Promise.all используйте версию кода forEach для рендеринга спрайтов в соответствии с файлом json:

Promise.all([
  loadBackgroundSprites(),
  loadLevel('1-1')
]).then(([sprites,level]) => {
  console.info(level);
  level.backgrounds.forEach(background => {
    drawBackground(background, context, sprites)
  });


I hope that helps with the tutorial.

Но как сделать так, чтобы объект спрайтов содержал плитку для ground? Обе функции loadDirtSprite() определяют землю для объекта спрайтов. Почему тогда он не работает?

Limpuls 03.05.2018 15:44

Я снова обновил свой OP с помощью рефакторинга кода. Та же ошибка .. Мне хочется сдаться.

Limpuls 03.05.2018 18:20
youtube.com/watch?v=FF93S8rLL_Q&t=350s 6:00 - 8:25 для парня он отлично работает даже с моим старым кодом:?
Limpuls 03.05.2018 18:23

Ну ладно, разобрался. Похоже, мой цикл не проходит через второй элемент «земля» в JSON. Все еще пытаюсь понять, почему, но если я жестко закодирую его напрямую, без использования JSON, он будет работать нормально.

Limpuls 03.05.2018 21:18

Вы совсем не поняли моего ответа. Я посмотрю, смогу ли я улучшить его за выходные.

traktor 04.05.2018 04:13

Спасибо, это действительно поможет!

Limpuls 04.05.2018 15:12

Я заменил ответ. Есть проблема со спрайт-листами, которую я обнаружил только сегодня :-)

traktor 06.05.2018 12:34

Я знаю, что это было довольно давно. Но когда вы создали новое обещание в модуле loader.js - обязательно добавьте .catch ((err) => console.info (err))

Также после обновления, если вы все еще видите проблему, попробуйте Ctrl + Shift + R обновить статический http-кеш, который все еще может кэшировать ваш старый код. Если это поможет, дайте мне знать!

loader.js

export function loadImage(url) {
    return new Promise(resolve => {
        const image = new Image();
        image.addEventListener('load', () => {
            resolve(image);
        });
        image.src = url;
    }).catch((err) => console.info(err)); <--------ADD THIS
}

Другие вопросы по теме