1. 效果展示
2. 技术细节
2.1. Viewport
A viewport represents a polygonal (normally rectangular) area in computer graphics that is currently being viewed. In web browser terms, it refers to the part of the document you're viewing which is currently visible in its window (or the screen, if the document is being viewed in full screen mode). Content outside the viewport is not visible onscreen until scrolled into view.
This becomes important, for example, on mobile devices, where a pinching gesture can usually be used to zoom in and out on a site's contents. The rendered document doesn't change in any way, so the layout viewport remains the same as the user adjusts the zoom level. Instead, the visual viewport is updated to indicate the area of the page that they can see.
2.2. Element.getBoundingClientRect()
The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.
示例:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">
html,
body {
width: 100%;
height: 100%;
}
body {
margin: 0;
}
.box {
position: absolute;
left: 300px;
top: 300px;
width: 400px;
height: 200px;
padding: 20px;
margin: 100px auto;
background: purple;
}
</style>
</head>
<body>
<div class="box"></div>
<div class="result"></div>
<script type="text/javascript">
let elem = document.querySelector('div.box');
let rect = elem.getBoundingClientRect();
const resultDiv = document.querySelector("div.result");
resultDiv.innerHTML = "";
for (var key in rect) {
if(typeof rect[key] !== 'function') {
let para = document.createElement('div');
para.textContent = `${ key } : ${ rect[key] }`;
resultDiv.appendChild(para);
}
}
</script>
</body>
</html>
2.3. Touch
示例:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">
#wrapper{
width: 600px;
height: 600px;
margin: 50px auto;
border: 3px solid blue;
}
#viewer{
position: fixed;
top: 50%;
transform: translate(0, -50%);
border: 1px solid green;
}
</style>
</head>
<body>
<div id="wrapper">
</div>
<pre id="viewer">
</pre>
<script type="text/javascript">
const ele = document.getElementById("wrapper");
const viewer = document.getElementById("viewer");
ele.addEventListener("touchmove", function(e) {
e.preventDefault();
var touch = e.targetTouches[0];
if (touch) {
viewer.innerHTML = `touchmove:
clientX:${touch.clientX}
clientY:${touch.clientY}`;
}
}, false);
</script>
</body>
</html>
2.4. Canvas
2.4.1. 填充图片
CanvasRenderingContext2D.drawImage():
语法:
void ctx.drawImage(image, dx, dy);
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
示例:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="text/javascript">
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const image = new Image(60, 45); // Using optional size for image
image.onload = function(){ // Draw when image has loaded
// Use the intrinsic size of image in CSS pixels for the canvas element
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
// Will draw the image as 300x227, ignoring the custom size of 60x45
// given in the constructor
ctx.drawImage(this, 0, 0);
// To use the custom size we'll have to specify the scale parameters
// using the element's width and height properties - lets draw one
// on top in the corner:
ctx.drawImage(this, 0, 0, this.width, this.height);
};
// Load an image of intrinsic size 300x227 in CSS pixels
image.src = 'https://media.prod.mdn.mozit.cloud/attachments/2013/06/22/5397/7a3ec0cae64a95ad454ac3bc2c71c004/rhino.jpg';
</script>
</body>
</html>
2.4.2. 填充文本
CanvasRenderingContext2D.fillText():
语法:
CanvasRenderingContext2D.fillText(text, x, y [, maxWidth]);
示例:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<canvas id="canvas" width="400" height="150"></canvas>
<script type="text/javascript">
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.font = '50px serif';
ctx.fillText('Hello world', 50, 60);
ctx.fillText('Hello world', 50, 120, 140);
</script>
</body>
</html>
2.4.3. 绘制圆弧路径
CanvasRenderingContext2D.arc():
语法:
void ctx.arc(x, y, radius, startAngle, endAngle [, anticlockwise]);
示例:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<canvas id="canvas" width="400" height="150"></canvas>
<script type="text/javascript">
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.rect(10, 20, 150, 100);
ctx.stroke();
</script>
</body>
</html>
2.4.4. 合成操作
CanvasRenderingContext2D.globalCompositeOperation:
语法:
3. 综合示例
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
<style>
.bg {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .2);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.scratch-card{
width: 2.96rem;
height: 3.45rem;
background: url(./red-envelope.png) center center/contain no-repeat;
position: relative;
}
.scratch-card > .scrape-area {
width: 2.40rem;
height: 1.20rem;
position: absolute;
bottom: 1rem;
left: 50%;
transform: translate(-50%, 0);
}
.scratch-card > .scrape-area > .award {}
.scratch-card > .scrape-area > canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.scratch-card > .scrape-area > canvas.finish {
display: none;
}
.scratch-card > .footer {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div class="bg">
<div class="scratch-card">
<div class="scrape-area">
<div class="award">
<h1>奖品:旋风冲锋</h1>
</div>
<canvas id="scrape-canvas"></canvas>
</div>
<img class="footer" src="footer.png"/>
</div>
</div>
<script type="text/javascript">
!function(e, t) {
var n = t.documentElement,d = e.devicePixelRatio || 1;
function i() {
var e = n.clientWidth / 3.75;
n.style.fontSize = e + "px"
}
t.body.style.fontSize = "16px";
i();
e.addEventListener("resize", i);
e.addEventListener("pageshow", function(e) {
e.persisted && i()
});
2 <= d;
} (window, document)
</script>
<script>
function initFestivalCanvas(){
var remain = 100; // (次)
const bridge = document.getElementById("scrape-canvas");
const bridgeCanvas = bridge.getContext('2d');
const img = new Image();
img.onload = function(){
bridgeCanvas.drawImage(img, 0, 0, bridge.width, bridge.height);
bridgeCanvas.font = "36px bold 微软雅黑";
bridgeCanvas.fillStyle = "red";
bridgeCanvas.fillText("刮我一下", 75, 90);
}
img.src = './scrape-area-inner.png';
function detectLeftButton(event) {
if ('buttons' in event) {
return event.buttons === 1;
} else if ('which' in event) {
return event.which === 1;
} else {
return event.button === 1;
}
}
function getBrushPos(xRef, yRef) {
var bridgeRect = bridge.getBoundingClientRect();
return {
x: xRef-bridgeRect.left,
y: yRef-bridgeRect.top
};
}
function drawDot(mouseX,mouseY){
bridgeCanvas.beginPath();
bridgeCanvas.arc(mouseX, mouseY, 15, 0, 2*Math.PI, true);
bridgeCanvas.fillStyle = '#000';
bridgeCanvas.globalCompositeOperation = "destination-out";
bridgeCanvas.fill();
}
bridge.addEventListener("touchmove", function(e) {
e.preventDefault();
const touch = e.targetTouches[0];
if (touch) {
const brushPos = getBrushPos(touch.clientX, touch.clientY);
drawDot(brushPos.x, brushPos.y);
if(remain-- < 0){
const ele = document.getElementById("scrape-canvas");
ele.className = ele.className + " " + "finish";
}
}
}, false);
}
initFestivalCanvas();
</script>
</body>
</html>
参考:
Element.getBoundingClientRect(): https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect Viewport: https://developer.mozilla.org/en-US/docs/Glossary/Viewport https://developer.mozilla.org/en-US/docs/Glossary/visual_viewport https://developer.mozilla.org/en-US/docs/Glossary/Layout_viewport A tale of two viewports: https://www.quirksmode.org/mobile/viewports.html https://www.quirksmode.org/mobile/viewports2.html Touch: https://developer.mozilla.org/en-US/docs/Web/API/Touch https://developer.mozilla.org/zh-CN/docs/Web/API/TouchEvent CanvasRenderingContext2D.arc() https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/arc globalCompositeOperation: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation