방송대 HTML5웹프로그래밍 14강: 미디어 상태 동기화와 드래그 앤 드롭
재생 버튼을 눌렀는데 화면 표시가 실제 영상과 어긋나거나, 드롭 영역까지 파일을 끌어왔는데 아무 일도 일어나지 않는다면 단순히 이벤트 이름을 더 외워서는 해결하기 어렵습니다. 미디어 제어와 드래그 앤 드롭은 모두 상태를 가진 대상, 상태를 바꾸는 명령, 변화를 알리는 이벤트의 연결 문제입니다. 이 글은 두 API를 관계망으로 다시 조립하여 어느 요소의 상태를 읽고, 어느 이벤트에서 데이터를 전달하며, 실패했을 때 어디부터 점검해야 하는지 판단할 수 있게 합니다.
두 기능은 서로 다른 상태 전달망이다
직접 구성한 학습 상황으로 온라인 강의 대시보드를 생각해 봅시다. 위쪽에는 재생·진행률·음량·배속을 조절하는 영상 플레이어가 있고, 아래쪽에는 학습 카드를 ‘할 일’에서 ‘완료’ 칸으로 옮기거나 과제 파일을 내려놓는 영역이 있습니다. 두 기능 모두 사용자의 입력으로 시작하지만 상태가 머무는 장소와 이벤트의 이동 방향은 다릅니다.
| 기능 | 상태의 실제 소유자 | 중간 연결 | 화면이 따라가는 근거 |
|---|---|---|---|
| 미디어 제어 | audio 또는 video 요소 | 속성·메서드 | play, timeupdate, volumechange 등의 이벤트 |
| 드래그 앤 드롭 | 출발지와 목적지 DOM 요소 | dataTransfer | dragstart부터 drop까지의 이벤트 |
미디어에서는 재생 버튼의 글자가 상태 그 자체가 아닙니다. 실제 상태는 미디어 요소에 있고 버튼은 그 상태를 비추는 표시 장치입니다. 드래그 앤 드롭에서는 마우스 아래 보이는 요소만으로 출발지와 목적지가 연결되지 않습니다. 어떤 데이터를 옮기는지 dataTransfer에 기록하고 목적지에서 같은 형식으로 꺼내야 합니다.
판단 핵심: UI가 어긋나면 먼저 “진짜 상태가 어디에 있는가?”를 찾고, 전달이 끊기면 “어느 이벤트가 다음 단계의 조건을 열어 주는가?”를 찾습니다.
미디어 요소가 재생 상태의 기준점이다
audio와 video는 모두 같은 미디어 제어 인터페이스를 바탕으로 동작하므로 주요 속성·메서드·이벤트를 비슷하게 사용할 수 있습니다. 다음 예는 여러 미디어 소스와 대체 문구를 가진 비디오입니다. preload="metadata"는 처음부터 전체 파일을 재생용으로 내려받으라는 뜻이 아니라 재생 시간 같은 메타데이터를 우선 확보하려는 설정입니다.
<video id="lectureVideo" controls preload="metadata"
poster="lecture-cover.jpg" playsinline>
<source src="lecture.webm" type="video/webm">
<source src="lecture.mp4" type="video/mp4">
이 브라우저에서는 비디오를 재생할 수 없습니다.
</video>
controls는 브라우저 기본 제어 UI를 표시하고, poster는 재생 전 대표 이미지를 지정합니다. 커스텀 플레이어를 만들 때는 기본 controls를 생략하고 별도의 버튼과 범위 입력 요소를 연결할 수 있습니다. 이때 버튼 자체에 재생 여부를 따로 저장하기보다 미디어 요소의 상태를 읽는 편이 일관성을 유지하기 쉽습니다.
| 속성 | 무엇을 나타내는가 | 읽기·쓰기 판단 | 주의할 경계 |
|---|---|---|---|
src | 미디어 파일 경로 | 읽기·쓰기 | 경로 변경 뒤 새 자원을 불러오는 흐름 확인 |
currentTime | 현재 재생 위치(초) | 읽기·쓰기 | 값을 대입하면 해당 위치로 탐색 |
duration | 전체 재생 길이(초) | 읽기 중심 | 메타데이터 전에는 유효한 길이가 없을 수 있음 |
paused | 일시 정지 여부 | 읽기 중심 | 버튼 문구가 아니라 이 상태로 분기 |
volume | 0.0부터 1.0 사이 음량 | 읽기·쓰기 | 0과 음소거 상태는 관련되지만 같은 속성은 아님 |
muted | 음소거 여부 | 읽기·쓰기 | 음량 값을 보존한 채 소리를 끌 수 있음 |
playbackRate | 재생 배속 | 읽기·쓰기 | 선택 UI와 실제 값을 다시 동기화 |
videoWidth·videoHeight | 원본 영상의 픽셀 크기 | 읽기 전용 | 화면에 표시한 CSS 크기와 구분 |
메서드는 명령하고 이벤트는 결과를 알린다
play(), pause(), load()는 동작을 요청하는 메서드입니다. 반면 play, pause, loadedmetadata 같은 이벤트는 실제 상태 변화가 일어났음을 알립니다. 커스텀 버튼 클릭 핸들러에서 버튼 문구까지 즉시 바꾸면 키보드, 다른 스크립트, 미디어 자체 종료처럼 버튼을 거치지 않은 변화가 UI에 반영되지 않을 수 있습니다.
const video = document.querySelector("#lectureVideo");
const toggleButton = document.querySelector("#toggleButton");
toggleButton.addEventListener("click", async () => {
if (video.paused) {
try {
await video.play();
} catch (error) {
console.log("재생 요청을 완료하지 못했습니다.", error);
}
} else {
video.pause();
}
});
video.addEventListener("play", () => {
toggleButton.textContent = "일시정지";
toggleButton.setAttribute("aria-label", "영상 일시정지");
});
video.addEventListener("pause", () => {
toggleButton.textContent = "재생";
toggleButton.setAttribute("aria-label", "영상 재생");
});
play()는 재생 시작 요청의 성공 여부를 다루는 Promise를 반환하므로 실패 가능성을 처리합니다. 버튼 클릭은 명령만 내리고, 버튼 문구와 접근성 이름은 실제 play·pause 이벤트를 근거로 바꿉니다. 재생이 끝난 뒤 다음 항목이나 다시 재생 UI를 보여 주려면 ended 이벤트를 사용할 수 있습니다. 별도의 stop() 메서드는 없으므로 처음으로 되돌리는 정지는 pause() 후 currentTime = 0으로 구성합니다.
function stopVideo() {
video.pause();
video.currentTime = 0;
}
진행률은 메타데이터와 현재 시간을 연결한다
진행 바의 최댓값은 전체 길이인 duration, 현재 값은 currentTime입니다. 전체 길이는 메타데이터가 준비된 뒤 설정하고, 재생 중에는 timeupdate에서 현재 값을 갱신합니다. 사용자가 진행 바를 움직이면 반대 방향으로 currentTime에 값을 써서 탐색합니다.
const progress = document.querySelector("#progress");
const timeText = document.querySelector("#timeText");
function formatTime(seconds) {
const minute = Math.floor(seconds / 60);
const second = Math.floor(seconds % 60)
.toString()
.padStart(2, "0");
return `${minute}:${second}`;
}
function updateTimeUI() {
timeText.textContent =
`${formatTime(video.currentTime)} / ${formatTime(video.duration)}`;
}
video.addEventListener("loadedmetadata", () => {
progress.max = video.duration;
updateTimeUI();
});
video.addEventListener("timeupdate", () => {
progress.value = video.currentTime;
updateTimeUI();
});
progress.addEventListener("input", (event) => {
video.currentTime = Number(event.target.value);
});
직접 계산해 보면 전체 길이 367초는 Math.floor(367 / 60) = 6분, 나머지 367 % 60 = 7초이므로 6:07입니다. 초를 두 자리로 맞추지 않으면 6:7처럼 보입니다. 또 duration이 아직 유효하지 않은 시점에 포맷 함수를 호출하면 정상적인 시간 표시를 만들기 어려우므로 초기 화면은 별도로 두고 loadedmetadata 이후 전체 시간을 연결합니다.
timeupdate는 재생 위치 변화에 맞춰 UI를 갱신하는 신호입니다. 프레임마다 정확히 한 번 발생하는 타이머로 가정하지 말고, 이벤트가 올 때 현재 상태를 읽어 표시하는 용도로 사용합니다.
음량과 배속도 양방향 동기화가 필요하다
슬라이더 입력으로 volume을 바꾸는 방향만 구현하면 다른 코드가 음량을 바꾸거나 음소거 버튼이 눌렸을 때 화면이 어긋날 수 있습니다. 입력은 미디어 상태를 바꾸고, volumechange 이벤트는 바뀐 상태를 다시 모든 UI에 반영해야 합니다.
volumeSlider.addEventListener("input", (event) => {
video.volume = Number(event.target.value);
if (video.volume > 0 && video.muted) {
video.muted = false;
}
});
muteButton.addEventListener("click", () => {
video.muted = !video.muted;
});
video.addEventListener("volumechange", () => {
const silent = video.muted || video.volume === 0;
muteButton.textContent = silent ? "소리 켬" : "음소거";
volumeSlider.value = silent ? 0 : video.volume;
});
volume === 0과 muted === true는 사용자에게 모두 소리가 들리지 않는 상태지만 내부 의미는 다릅니다. 음소거를 해제하면 이전 음량으로 돌아갈 수 있으므로 두 속성을 하나로 덮어쓰지 않습니다. 같은 원리로 배속 선택 상자는 change에서 playbackRate를 바꾸고, ratechange에서 실제 배속을 다시 읽어 화면을 맞춥니다.
speedSelect.addEventListener("change", (event) => {
video.playbackRate = Number.parseFloat(event.target.value);
});
video.addEventListener("ratechange", () => {
speedSelect.value = String(video.playbackRate);
});
버퍼링 표시는 waiting과 playing 사이의 상태 전이다
재생 중 다음 데이터를 충분히 받지 못하면 waiting 이벤트가 발생할 수 있고, 다시 재생이 이어지면 playing 이벤트가 발생합니다. 로딩 표시를 버튼 클릭에 묶지 않고 이 상태 이벤트에 묶어야 실제 버퍼링과 일치합니다. 처음 재생할 만큼 준비된 경우의 안전장치로 canplay에서도 표시를 숨길 수 있습니다.
video.addEventListener("waiting", () => {
loadingSpinner.classList.add("active");
});
function hideSpinner() {
loadingSpinner.classList.remove("active");
}
video.addEventListener("playing", hideSpinner);
video.addEventListener("canplay", hideSpinner);
waiting은 사용자가 의도적으로 누른 pause와 다릅니다. 둘 다 화면이 멈춘 것처럼 보이지만, 전자는 데이터 대기이고 후자는 미디어의 일시 정지 상태입니다. 원인을 구분해야 스피너와 재생 버튼을 서로 다른 이벤트로 제어할 수 있습니다.
드래그 앤 드롭은 출발지·운반자·목적지로 나뉜다
네이티브 드래그 앤 드롭에서 먼저 분리해야 할 것은 세 역할입니다. 끌리는 source, 내려놓는 target, 그 사이에서 데이터를 운반하는 dataTransfer입니다. 일반 요소를 출발지로 만들려면 draggable="true"를 지정합니다. 드롭 존은 속성 하나로 완성되지 않고 이벤트 처리로 허용 상태를 만들어야 합니다.
| 역할 | 주요 사건 | 해야 할 일 | 실패하면 보이는 증상 |
|---|---|---|---|
| 출발지 | dragstart, drag, dragend | 식별 데이터 저장, 끌림 상태 표시·해제 | 무엇을 옮겼는지 목적지가 알 수 없음 |
| 운반자 | 모든 DnD 이벤트의 dataTransfer | 형식과 데이터, 허용 작업, 파일 목록 전달 | 형식 불일치로 빈 데이터가 나옴 |
| 목적지 | dragenter, dragover, dragleave, drop | 드롭 허용, 피드백, 데이터 읽기와 실제 처리 | drop이 발생하지 않거나 기본 동작이 실행됨 |
이벤트의 위치도 중요합니다. dragstart와 dragend는 끌리는 요소 쪽에서, dragenter·dragover·dragleave·drop은 내려놓을 영역 쪽에서 다룹니다. drag와 dragover는 이동 중 반복해서 발생하므로 매번 무거운 작업을 넣기보다 필요한 허용 처리와 피드백에 집중합니다.
강의자료는 네이티브 DnD API가 마우스 중심으로 동작하므로 복잡한 목록 정렬이나 터치 환경에서는 별도의 입력 처리나 이를 지원하는 도구가 필요하다고 설명합니다. 따라서 데스크톱에서 동작하는 예제를 곧바로 모바일 상호작용까지 보장하는 구현으로 해석하면 안 됩니다.
dragover가 목적지를 열고 drop이 작업을 확정한다
가장 자주 만나는 실패는 drop 처리기를 작성했는데도 실행되지 않는 경우입니다. 목적지의 dragover에서 preventDefault()를 호출해야 브라우저에 이 영역이 드롭을 받을 수 있음을 알릴 수 있습니다. drop에서도 기본 동작을 막으면 파일 열기 같은 브라우저 동작 대신 애플리케이션의 처리를 수행할 수 있습니다.
target.addEventListener("dragenter", (event) => {
event.preventDefault();
target.classList.add("drag-over");
});
target.addEventListener("dragover", (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
});
target.addEventListener("dragleave", () => {
target.classList.remove("drag-over");
});
target.addEventListener("drop", (event) => {
event.preventDefault();
target.classList.remove("drag-over");
// 여기에서 데이터를 읽고 이동·복사 작업을 수행한다.
});
시각적 피드백은 장식이 아니라 “지금 놓을 수 있는가?”를 알려 주는 상태 표시입니다. 들어올 때 클래스를 추가했다면 벗어날 때와 놓았을 때 모두 제거해야 합니다. drop에서만 제거하면 드롭 존 밖에서 놓았을 때 강조 상태가 남을 수 있습니다.
dataTransfer는 형식까지 맞아야 전달된다
setData(format, data)는 드래그 시작 시 데이터를 담고, getData(format)는 드롭 시 같은 형식의 데이터를 꺼냅니다. 형식은 저장과 읽기에서 일치해야 합니다. clearData()는 저장 데이터를 비우며 형식을 생략하면 전체 데이터를 대상으로 합니다.
source.addEventListener("dragstart", (event) => {
event.dataTransfer.setData("text/plain", source.id);
event.dataTransfer.effectAllowed = "move";
source.classList.add("dragging");
});
source.addEventListener("dragend", () => {
source.classList.remove("dragging");
});
target.addEventListener("drop", (event) => {
event.preventDefault();
const sourceId = event.dataTransfer.getData("text/plain");
const movedElement = document.getElementById(sourceId);
if (movedElement) {
target.append(movedElement);
}
});
effectAllowed는 출발지가 허용하는 작업의 범위를 정하고, dropEffect는 목적지가 현재 수행하려는 작업을 표시합니다. 출발지가 이동만 허용했는데 목적지가 복사를 선택하면 계약이 맞지 않습니다. 즉 커서 모양을 임의로 고르는 두 독립 속성이 아니라 허용 범위 안에서 목적지의 작업을 선택하는 관계입니다.
연결 공식: 출발지에서 setData("text/plain", 값) → 목적지의 dragover에서 드롭 허용 → drop에서 getData("text/plain") → 실제 DOM 이동 또는 복사 순서로 추적합니다.
강의 대시보드의 카드는 ID만 운반하면 된다
직접 구성한 예로 ‘복습 예정’ 카드를 ‘복습 완료’ 열로 옮긴다고 합시다. 드래그 중 카드의 전체 HTML을 전달할 필요는 없습니다. 출발지 ID만 문자열로 운반하고, 드롭 시 문서에서 실제 요소를 찾아 목적지에 붙이면 됩니다.
<div class="column" id="review-todo">
<div class="card" id="lesson-14" draggable="true">
14강 미디어·DnD 복습
</div>
</div>
<div class="column" id="review-done">
<h3>복습 완료</h3>
</div>
const cards = document.querySelectorAll(".card");
const columns = document.querySelectorAll(".column");
cards.forEach((card) => {
card.addEventListener("dragstart", (event) => {
event.dataTransfer.setData("text/plain", card.id);
event.dataTransfer.effectAllowed = "move";
card.classList.add("dragging");
});
card.addEventListener("dragend", () => {
card.classList.remove("dragging");
});
});
columns.forEach((column) => {
column.addEventListener("dragover", (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
});
column.addEventListener("drop", (event) => {
event.preventDefault();
const cardId = event.dataTransfer.getData("text/plain");
const card = document.getElementById(cardId);
if (card) column.append(card);
});
});
append()에 기존 DOM 요소를 넘기면 복제본을 만드는 것이 아니라 그 요소의 부모가 새 열로 바뀝니다. 복사를 원한다면 같은 코드를 쓰고 속성 이름만 copy로 바꾸는 것으로는 부족하며, 복제할 요소를 별도로 만들어야 합니다. 작업 의미와 DOM 처리 방식이 일치해야 합니다.
외부 파일은 files에서 꺼내고 안전하게 표시한다
운영체제에서 끌어온 파일은 setData()로 직접 담은 문자열과 달리 dataTransfer.files에 FileList 형태로 들어옵니다. 드롭 시 목록이 비었는지 확인하고 배열로 변환해 각 파일을 처리할 수 있습니다.
dropZone.addEventListener("dragover", (event) => {
event.preventDefault();
});
dropZone.addEventListener("drop", (event) => {
event.preventDefault();
const files = event.dataTransfer.files;
if (files.length === 0) return;
Array.from(files).forEach(addFileToList);
});
function addFileToList(file) {
const item = document.createElement("li");
const name = document.createElement("strong");
const size = document.createElement("span");
const sizeInMB = file.size / (1024 * 1024);
name.textContent = file.name;
size.textContent = `${sizeInMB.toFixed(2)} MB`;
item.append(name, size);
fileList.append(item);
}
파일 크기가 1,572,864바이트라면 강의 코드와 같은 표시 관례에서 1,572,864 ÷ 1,048,576 = 1.5이므로 결과는 1.50 MB입니다. 파일 이름은 외부에서 들어온 문자열이므로 예제에서는 innerHTML로 조립하지 않고 textContent로 넣어 마크업으로 해석되지 않게 했습니다. 이 코드는 선택된 파일 정보를 화면에 보여 주는 학습용 예이며, 실제 서버 업로드 전송까지 수행하지는 않습니다.
증상에서 끊어진 연결을 역추적한다
두 API의 코드는 이벤트가 많아 오류가 난 줄만 보고 원인을 찾기 어렵습니다. 화면에 드러난 증상에서 상태 전달망을 거꾸로 따라가면 점검 범위를 줄일 수 있습니다.
| 증상 | 첫 점검 | 다음 점검 | 교정 기준 |
|---|---|---|---|
| 재생됐는데 버튼은 ‘재생’ | play 이벤트 연결 | 버튼 문구를 클릭에서만 바꾸는지 | 실제 미디어 이벤트를 UI 기준으로 삼음 |
| 전체 시간이 정상 표시되지 않음 | loadedmetadata 시점 | duration 유효성 | 메타데이터 후 최댓값·문구 설정 |
| 버퍼링 뒤 스피너가 남음 | playing 처리 | canplay 안전장치 | 대기 시작과 재생 재개를 서로 다른 이벤트로 처리 |
drop이 발생하지 않음 | dragover의 preventDefault() | 목적지에 리스너를 달았는지 | 목적지를 먼저 드롭 가능 상태로 만듦 |
| 드롭 데이터가 빈 문자열 | setData 실행 시점 | 저장·읽기 형식 일치 | dragstart와 drop의 format을 맞춤 |
| 드롭 후 강조 테두리가 남음 | drop의 클래스 제거 | dragleave의 클래스 제거 | 성공·이탈 두 종료 경로를 모두 정리 |
짧게 자가 점검해 봅시다. “버튼이 상태를 소유한다”라고 설명했다면 미디어 요소와 이벤트의 역할을 다시 구분해야 합니다. “드롭하면 브라우저가 알아서 요소를 옮긴다”라고 설명했다면 데이터 전달과 실제 DOM 변경 단계를 빠뜨린 것입니다. 각 기능을 상태 소유자, 명령, 이벤트, UI 반영의 네 칸으로 말할 수 있으면 관계가 연결된 것입니다.
핵심 개념 정리
- 미디어 관계망: UI 입력은 속성·메서드로 미디어 요소를 바꾸고, 실제 변화 이벤트가 UI를 다시 동기화합니다.
- 시간 관계:
loadedmetadata에서 전체 길이를 준비하고timeupdate에서 현재 위치를 반영합니다. - 소리·속도 관계:
volume과muted를 구분하고,volumechange·ratechange로 표시를 실제 상태에 맞춥니다. - 드래그 관계망: 출발지는
dragstart에서 데이터를 담고, 목적지는dragover에서 드롭을 허용한 뒤drop에서 꺼내 처리합니다. - 작업 계약:
effectAllowed의 허용 범위와dropEffect의 선택이 호환되어야 하며, 실제 이동·복사는 DOM 코드가 수행합니다. - 파일 경로: 외부 파일은
dataTransfer.files에서 읽고, 파일 이름 같은 외부 문자열은 텍스트로 안전하게 표시합니다.
새 기능을 만들거나 오류를 고칠 때는 먼저 실제 상태의 소유자를 찾고, 사용자의 입력이 어떤 명령을 거쳐 그 상태를 바꾸는지 추적합니다. 이어서 어느 이벤트가 변화를 증명하는지, UI가 그 이벤트에서 다시 상태를 읽는지 확인합니다. 드래그 앤 드롭이라면 이 순서에 출발지의 데이터 저장, 목적지의 허용, 같은 형식의 데이터 읽기, 실제 처리와 종료 피드백 정리를 더합니다. 이 관계도를 기준으로 보면 이벤트 이름의 목록이 아니라 끊어질 수 있는 연결로 코드를 진단할 수 있습니다.
예상문제 10선
1. 커스텀 미디어 플레이어에서 화면 UI를 실제 재생 상태와 일치시키는 원칙으로 가장 적절한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 버튼 글자는 표시 결과일 뿐 재생 상태의 소유자가 아니므로 판단 방향이 거꾸로입니다.
- ② 오답: 별도 변수는 미디어 자체 상태와 달라질 수 있어 두 상태를 동기화하는 추가 문제가 생깁니다.
- ③ 정답: 상태의 기준점인 미디어 요소와 실제 변화 이벤트를 사용하므로 다른 조작 경로도 같은 UI에 반영됩니다.
- ④ 오답: 재생 요청 실패나 다른 코드의 상태 변경이 생기면 클릭 시점의 UI가 실제 상태와 어긋납니다.
2. currentTime과 duration의 차이를 올바르게 설명한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 진행 바의 현재 값과 최댓값을 각각 연결하는 정확한 역할 구분입니다.
- ② 오답: 탐색은
currentTime에 값을 쓰며 전체 길이인duration을 탐색 위치로 사용하지 않습니다. - ③ 오답: 두 속성은 각각 현재 위치와 전체 길이라는 서로 다른 시간 정보를 나타냅니다.
- ④ 오답: 영상 크기는
videoWidth·videoHeight가 담당하고 두 속성은 초 단위 시간입니다.
3. 전체 길이 367초를 본문의 formatTime() 방식으로 표시한 결과는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: 367을 60으로 나눈 나머지는 7이며 초를 70으로 계산하지 않습니다.
- ② 정답: 몫은 6분, 나머지는 7초이고 초를 두 자리로 채우므로 6:07입니다.
- ③ 오답: 몫과 나머지의 위치를 바꾸었고 6을 분으로 올림할 근거가 없습니다.
- ④ 오답: 초를 60 미만의 나머지로 바꾸지 않아 시간 형식이 성립하지 않습니다.
4. 재생 버튼을 눌렀을 때 요청 실패까지 처리하고 실제 상태와 버튼 문구를 맞추는 설계는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 재생이 거부되면 영상은 멈췄는데 버튼만 일시정지로 바뀌는 불일치가 생깁니다.
- ② 오답: 전체 길이는 재생 명령이 아니며
timeupdate는 재생·일시정지 자체의 전용 신호가 아닙니다. - ③ 오답:
paused는 현재 상태를 확인하는 값이며 재생은play()로 요청해야 합니다. - ④ 정답: 명령의 실패 가능성과 실제 상태 이벤트를 모두 반영해 UI를 미디어 요소에 종속시킵니다.
5. 영상이 데이터 부족으로 멈춘 동안만 로딩 표시를 보이게 하는 이벤트 연결은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: 사용자가 의도적으로 일시 정지한 경우까지 버퍼링으로 표시하고 위치 갱신에 숨김을 맡깁니다.
- ② 정답: 데이터 대기로 재생이 멈춘 시점과 충분한 데이터를 받아 다시 재생한 시점을 직접 연결합니다.
- ③ 오답: 메타데이터 준비와 음량 변화는 버퍼링 시작·종료를 나타내지 않습니다.
- ④ 오답: 배속 변경과 재생 완료는 네트워크 데이터 대기의 양 끝이 아닙니다.
6. 드래그 시작 시 데이터를 담아 목적지 이벤트까지 전달하는 객체는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: DnD 이벤트 객체에 포함되어 형식이 지정된 데이터와 허용 작업, 파일 목록을 운반합니다.
- ② 오답: 미디어의 현재 재생 위치를 나타내는 속성이며 드래그 데이터 운반자가 아닙니다.
- ③ 오답: DOM 요소의 CSS 클래스를 추가·제거하는 인터페이스로 시각적 피드백에는 쓰이지만 데이터를 전달하지 않습니다.
- ④ 오답: 미디어 재생 속도를 정하는 속성으로 DnD 출발지와 목적지를 연결하지 않습니다.
7. 텍스트 요소를 다른 영역으로 이동하는 드래그 앤 드롭의 핵심 순서로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 저장과 읽기의 시간 순서를 뒤집었고
dragleave는 이탈 피드백을 정리하는 사건입니다. - ② 오답:
dragend는 출발지의 종료 처리이며 목적지 허용 조건을 만드는 단계가 아닙니다. - ③ 오답: 진입만으로 이동을 확정하면 사용자가 놓기 전이며 데이터는 시작 단계에서 먼저 준비해야 합니다.
- ④ 정답: 출발지 준비, 목적지 허용, 드롭 확정이라는 관계망의 선후 조건을 모두 지킵니다.
8. effectAllowed와 dropEffect의 관계를 바르게 설명한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 파일 목록은
files가 담당하며 두 속성은 이동·복사·링크 같은 작업 효과를 조정합니다. - ② 오답: 데이터 형식과 값은
setData()로 저장하고 이 속성들은 작업 허용 관계를 표현합니다. - ③ 정답: 출발지의 계약 안에서 목적지가 현재 작업을 정해야 한다는 방향과 호환 조건이 정확합니다.
- ④ 오답: 역할 방향을 뒤집었고 파일 크기 표시는 어느 속성의 책임도 아닙니다.
9. 파일 크기 1,572,864바이트를 본문의 1024 × 1024 계산식과 표시 관례로 나타내면?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 두 번의 1024를 곱한 1,048,576으로 나누면 1.5이고 둘째 자리 표시로 1.50이 됩니다.
- ② 오답: 1,000,000으로 나눈 십진식 결과에 가까우며 문제에서 준
1024 × 1024식과 다릅니다. - ③ 오답: 1024로 한 번만 나눈 KB 값 1536을 다시 MB로 잘못 표기했습니다.
- ④ 오답: 분자와 분모를 뒤집어 계산한 값에 가까워 파일 크기 변환 방향이 반대입니다.
10. 카드를 끌어 열 위에 놓았지만 drop 이벤트가 발생하지 않는다. 가장 먼저 확인할 코드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답:
currentTime은 미디어 재생 위치이며 목적지가 드롭을 받을 조건과 무관합니다. - ② 오답:
volumechange는 음량·음소거 변화 이벤트로 DnD 허용 상태를 만들지 않습니다. - ③ 오답: 이탈 이벤트의 파일 크기 계산은 드롭 가능 여부를 바꾸지 않고 실행 시점도 맞지 않습니다.
- ④ 정답: 목적지의 기본 드롭 금지 동작을 막아야 해당 영역에서
drop을 받을 수 있습니다.
참고 자료와 작성 기준
이 글은 해당 차시 강의자료 42쪽 전체를 확인한 뒤 표지·목차·단원 구분·정리·다음 차시 예고를 본문과 구분하고, 미디어 제어와 드래그 앤 드롭을 상태 전달 관계망으로 재구성한 비공식 학습자료입니다. 강의자료의 범위 안에서 강의 대시보드 사례, 상태 소유자 추적, 367초 시간 계산, 파일 크기 검산, 안전한 파일명 표시와 증상별 연결 진단표를 새로 구성했습니다.
- 작성·편집: 올에이클래스 학습연구팀
- 주요 근거: 한국방송통신대학교 HTML5웹프로그래밍 14강 「HTML API: 미디어 제어, 드래그 앤 드롭」 강의자료
- 보충 자료: 외부 자료를 사용하지 않았으며, 강의자료의 설명 범위 안에서 예제와 진단 절차를 재구성했습니다.
- 편집 원칙: 올에이클래스 편집 정책
- 최종 내용 검토: 2026-09-05
댓글
댓글 쓰기