canvas中的拖拽、缩放、旋转(手机查看)

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta charset="utf-8"/>
<title>canvas中的拖拽,缩放,旋转</title>
<style type="text/css">
.canvas-container {
border: 1px solid #aaa;
width: 302px;
margin: 0 auto;
}

    .box-container {
        margin-top: 20px;
    }

    .red-box {
        background-color: #f00;
        width: 60px;
        height: 40px;
    }
</style>

</head>
<body>
<div class="canvas-container">
<canvas width='300' height="300"></canvas>
</div>
<div class="box-container">
<div class="red-box"></div>
</div>
</body>
</html>

<script type="text/javascript">
const BOX_PADDING = 10;
const ICON_HEIGHT = 20;
const ROTATE_ICON = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA70lEQVQ4T7XTL0vDURTG8c80aJVhEWx2u2AyTYNonmAwGi2Cgth8AWNBgzYZLKmwMJhYLL4Io9hFDP7hyJ2My+/nlLHTzj3P/d5znntvxYhRGXG/sQGWsI1nfGIGD7hI+U/jRR0c4QmneE/KSRxgFruDY+eAFSwjIEVxgkc0+8UccIcaXkoAE7hJmm/JIKCKM2wMuZku1vCWA6bQwvoQQAer+MgBkfdS8TWDzGMTDVyVjRB7trCIvQwQa3VMo43bMhPDk2MsYD85HtoAXOMch79dY78WRsaJc9hJD+oS8R4iAn5f5MG/v8bY/sKfO/kCL2skEWR80iIAAAAASUVORK5CYII=';
const DEL_ICON = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABIUlEQVRYR+3VPUvEQBSF4WdLsRdBQQsrURErC/31CtoJfiDICooigr22cnEDYdGdO5tgmqQJITNz3jn3zpmJgZ/JwPpGgNGB0YGsAzt4w2fy2K5iHY+l8RmAXdzhFad4Kiy6jTNsYh+3i8ZnAFZwjXChBNEWn+IAX10BYn7YeYmtBRBt8Wcc472PEjRrhKXnf0DMi5/MQEv61VH8G0SINDWPnafFY2KmB+Z30YYIwXiiNNXiywLEvIC4mL3jeynxrgBNP/w7wKAlyDRh6vg1jVXThNljGAEUiVnMgJoeqA2iNETGgdoojtMRwA847COK93CTuAeassadEcEUEHGR3fdxFxzhBR/FbP0ZsIYNXJXGZ0pQWqPT/xFgdGB0YHAHvgG1jEwhcd+TtgAAAABJRU5ErkJggg==';
const SCALE_ICON = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABJElEQVRYR+3UPS8EURTG8d82GolE0NArlT6ARqEiChrRKXTUWqUtJSpCQoNKlEqlL6CloJB4aVRyky2QnZk7szOzzb3NFPPkPP/z3HNux5BPZ8j+EkBKICVQJYExnCF8s84D9vFatOZVAXYKCq9iGvN4zNNWAShqaglXeMItttsEWMY5NrGASawMCjCOb3wVtB7ML7CGaxzVBXCKF+zmAPw3D9LaAEI3AWArA6CfeWsAWeatAOSZNw4QBu0Y672B63c7jc1AMD/prddNzmB2MYGNQdfw9xDGmgfPUYzgrS6Au8jOi17KP/9jnuJDLGImIvZS5kEcAzCLezzjssDhAO9lKGIAQr0p7GEup/hnbys+mgAoU7OUNjaBUkXLiBNASiAl8AO7pD8h3UM4owAAAABJRU5ErkJggg==';

class Stage {

    constructor(props) {

        this.canvas = props.canvas;
        this.ctx = this.canvas.getContext('2d');
        this.spriteList = [];

        const pos = this.canvas.getBoundingClientRect();
        this.canvasOffsetLeft = pos.left;
        this.canvasOffsetTop = pos.top;

        this.dragSpriteTarget = null;
        this.scaleSpriteTarget = null;
        this.rotateSpriteTarget = null;

        this.dragStartX = undefined;
        this.dragStartY = undefined;
        this.scaleStartX = undefined;
        this.scaleStartY = undefined;
        this.rotateStartX = undefined;
        this.rotateStartY = undefined;

        this.initEvent();

    }

    append(sprite) {
        this.spriteList.push(sprite);
        this.drawSprite();
    }

    initEvent() {
        this.canvas.addEventListener('touchstart', e => {
            this.handleTouchStart(e);
        });
        this.canvas.addEventListener('touchend', () => {
            this.handleTouchEnd();
        });
        this.canvas.addEventListener('touchmove', e => {
            this.handleTouchMove(e);
            e.preventDefault();
        }, { passive: false });
    }


    handleTouchStart(e) {
        const touchEvent = this.normalizeTouchEvent(e);

        if (!touchEvent) {
            return;
        }
        let target = null

        // 触摸在sprite上,可以拖动
        if (target = this.getTouchSpriteTarget(touchEvent)) {
            this.initDragEvent(target, touchEvent);
            return;
        }

        // 缩放
        if (target = this.getTouchTargetOfSprite(touchEvent, 'scaleIcon')) {
            this.initScaleEvent(target, touchEvent);
            return;
        }

        // 旋转
        if (target = this.getTouchTargetOfSprite(touchEvent, 'rotateIcon')) {
            this.initRotateEvent(target, touchEvent);
            return;
        }

        // 删除
        if (target = this.getTouchTargetOfSprite(touchEvent, 'delIcon')) {
            this.remove(target);
            return;
        }

    }

    handleTouchMove(e) {

        const touchEvent = this.normalizeTouchEvent(e);
        if (!touchEvent) {
            return;
        }
        const { touchX, touchY } = touchEvent;

        // 拖拽
        if (this.dragSpriteTarget) {
            this.reCalSpritePos(this.dragSpriteTarget, touchX, touchY);
            this.drawSprite();
            return;
        }

        // 缩放
        if (this.scaleSpriteTarget) {
            this.reCalSpriteSize(this.scaleSpriteTarget, touchX, touchY);
            this.drawSprite();
            return;
        }

        // 旋转
        if (this.rotateSpriteTarget) {
            this.reCalSpriteRotate(this.rotateSpriteTarget, touchX, touchY);
            this.drawSprite();
            return;
        }

    }

    handleTouchEnd() {
        if(this.rotateSpriteTarget) {
            this.rotateSpriteTarget.updateCoordinateByRotate();
        }
        if(this.scaleSpriteTarget) {
            this.scaleSpriteTarget.updateCoordinateByScale();
        }
        this.scaleSpriteTarget = null;
        this.dragSpriteTarget = null;
        this.rotateSpriteTarget = null;
    }

    // 初始化sprite的拖拽事件
    initDragEvent(sprite, { touchX, touchY }) {
        this.dragSpriteTarget = sprite;
        this.dragStartX = touchX;
        this.dragStartY = touchY;
    }

    // 初始化sprite的缩放事件
    initScaleEvent(sprite, { touchX, touchY }) {
        this.scaleSpriteTarget = sprite;
        this.scaleStartX = touchX;
        this.scaleStartY = touchY;
    }

    // 初始化sprite的旋转事件
    initRotateEvent(sprite, { touchX, touchY }) {
        this.rotateSpriteTarget = sprite;
        this.rotateStartX = touchX;
        this.rotateStartY = touchY;
    }

    // 通过触摸的坐标重新计算sprite的坐标
    reCalSpritePos(sprite, touchX, touchY) {
        const [oX, oY] = sprite.pos;
        const dirX = touchX - this.dragStartX;
        const dirY = touchY - this.dragStartY;
        sprite.resetPos(dirX, dirY);
        this.dragStartX = touchX;
        this.dragStartY = touchY;
    }

    // 通过触摸的【横】坐标重新计算sprite的大小
    reCalSpriteSize(sprite, touchX, touchY) {
        // 使用X轴方向作为缩放比例的判断标准
        const [centerX, centerY] = sprite.center;
        const startVector = [this.scaleStartX - centerX, this.scaleStartY - centerY];
        const endVector = [touchX - centerX, touchY - centerY];
        const dirVector = [touchX - this.scaleStartX, touchY - this.scaleStartY];
        const startVectorLength = Math.sqrt(Math.pow(startVector[0], 2) + Math.pow(startVector[1],2));
        const endVectorLength = Math.sqrt(Math.pow(endVector[0], 2) + Math.pow(endVector[1],2));
        const dirX = Math.abs(dirVector[0]);
        const dirY = Math.abs(dirVector[1]);
        let dir = dirX > dirY ? dirX : dirY;
        if(endVectorLength < startVectorLength) {
            dir = -dir;
        }
        sprite.resetSize(dir);
        this.scaleStartX = touchX;
        this.scaleStartY = touchY;
    }

    // 重新计算sprite的角度
    reCalSpriteRotate(sprite, touchX, touchY) {
        const [centerX, centerY] = sprite.center;
        const x1 = this.rotateStartX - centerX;
        const y1 = this.rotateStartY - centerY;
        const x2 = touchX - centerX;
        const y2 = touchY - centerY;

        // 因为sin函数
        const numerator =  x1 * y2 - y1 * x2;
        const denominator = Math.sqrt(Math.pow(x1, 2) + Math.pow(y1, 2)) * Math.sqrt(Math.pow(x2, 2) + Math.pow(
            y2, 2));
        const sin = numerator / denominator;
        const angleDir = Math.asin(sin);

        sprite.setRotateAngle(angleDir);
        this.rotateStartX = touchX;
        this.rotateStartY = touchY;
    }

    // 返回当前touch的sprite
    getTouchSpriteTarget({ touchX, touchY }) {
        return this.spriteList.reduce((sum, sprite) => { // 这里一直循环到最后,保证每一次移动的都是最后插入的sprite 
            if (this.checkIfTouchIn({ touchX, touchY }, sprite)) {
                sum = sprite;
            }
            return sum;
        }, null);
    }

    // 判断是否touch在了sprite中的某一部分上,返回这个sprite
    getTouchTargetOfSprite({ touchX, touchY }, part) {
        return this.spriteList.reduce((sum, sprite) => {
            if (this.checkIfTouchIn({ touchX, touchY }, sprite[part])) {
                sum = sprite;
            }
            return sum;
        }, null);
    }

    // 返回点击坐标
    normalizeTouchEvent(e) {
        const touches = [].slice.call(e.touches);
        if (touches.length > 1) { // 多点触摸,不做处理
            return;
        }
        const target = touches[0];
        const touchX = target.pageX - this.canvasOffsetLeft;
        const touchY = target.pageY - this.canvasOffsetTop;
        return {
            touchX,
            touchY
        }
    }


    // 判断是否在在某个sprite中移动。当前默认所有的sprite都是长方形的。
    checkIfTouchIn({ touchX, touchY }, sprite) {
        if (!sprite) {
            return false;
        }
        const [[x1, y1], [x2, y2], [x3, y3], [x4, y4]] = sprite.coordinate;
        const v1 = [x1 - touchX, y1 - touchY];
        const v2 = [x2 - touchX, y2 - touchY];
        const v3 = [x3 - touchX, y3 - touchY];
        const v4 = [x4 - touchX, y4 - touchY];
        if(
            (v1[0] * v2[1] - v2[0] * v1[1]) > 0 
            && (v2[0] * v4[1] -  v4[0] * v2[1]) > 0
            && (v4[0] * v3[1] - v3[0] * v4[1]) > 0
            && (v3[0] * v1[1] -  v1[0] * v3[1]) > 0
        ){
            return true;
        }
        return false;

    }

    // 从场景中删除
    remove(sprite) {
        this.spriteList = this.spriteList.filter(item => {
            return item.id !== sprite.id;
        });
        this.drawSprite();
    }

    drawSprite() {
        this.clearStage();
        this.spriteList.forEach(item => {
            item.draw(this.ctx);
        });
    }

    clearStage() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
}


class Sprite {

    constructor(props) {

        this.id = Date.now() + Math.floor(Math.random() * 10);
        this.pos = props.pos;
        this.size = props.size;
        this.baseSize = props.size;
        this.minSize = props.minSize;
        this.maxSize = props.maxSize;
        this.center = [props.pos[0] + props.size[0] / 2, props.pos[1] + props.size[1] / 2];
        this.delIcon = {};
        this.scaleIcon = {};
        this.rotateIcon = {};

        this.coordinate = this.setCoordinate(this.pos, this.size);
        
        this.rotateAngle = 0; // 一共旋转的角度
        this.rotateAngleDir = 0; // 每次旋转角度差值

        this.scalePercent = 1; // 一共缩放的比例
        this.parent = this;

        this.init();
    }

    setCoordinate(pos, size) {
        return [
            [pos[0], pos[1]],
            [pos[0] + size[0], pos[1]],
            [pos[0], pos[1] + size[1]],
            [pos[0] + size[0], pos[1] + size[1]]
        ];
    }

    setIconCoordinate(point) {
        return [
            [point[0] - ICON_HEIGHT / 2, point[1] - ICON_HEIGHT / 2],
            [point[0] + ICON_HEIGHT / 2, point[1] - ICON_HEIGHT / 2],
            [point[0] - ICON_HEIGHT / 2, point[1] + ICON_HEIGHT / 2],
            [point[0] + ICON_HEIGHT / 2, point[1] + ICON_HEIGHT / 2]
        ];
    }

    updateCoordinateByRotate() {
        const angle = this.rotateAngleDir;
        this.updateItemCoordinateByRotate(this, this.center, angle);
        this.updateItemCoordinateByRotate(this.delIcon, this.center, angle);
        this.updateItemCoordinateByRotate(this.scaleIcon, this.center, angle);
        this.updateItemCoordinateByRotate(this.rotateIcon, this.center, angle);
        this.rotateAngleDir = 0;
    }

    updateItemCoordinateByScale(sprite, center, scale) {
        const [centerX, centerY] = center;
        const coordinateVector = sprite.coordinate.map( point => {
            return [point[0]- centerX, point[1] - centerY];
        });
        const newCoordinateVector = coordinateVector.map( vector => {
            const [x, y] = vector;
            const newX = x * scale;
            const newY = y * scale;
            return [newX, newY];
        });
        sprite.coordinate = newCoordinateVector.map( vector => {
            return [vector[0] + centerX , vector[1] + centerY];
        });
    }

    getIconCenter(iconCoordinate) {
        const point1 = iconCoordinate[0];
        const point4 = iconCoordinate[3];
        const x = (point1[0] + point4[0]) / 2;
        const y = (point1[1] + point4[1]) / 2;
        return [x, y];
    }

    getIconCoordinateByIconCenter(center) {
        const [x, y] = center;
        return [
            [x - ICON_HEIGHT / 2, y - ICON_HEIGHT / 2],
            [x + ICON_HEIGHT / 2, y - ICON_HEIGHT / 2],
            [x - ICON_HEIGHT / 2, y + ICON_HEIGHT / 2],
            [x + ICON_HEIGHT / 2, y + ICON_HEIGHT / 2]
        ];
    }

    updateCoordinateByScale() {
        const scale = this.size[0] / this.baseSize[0];
       
        // 左上角旋转按钮
        const [rotateCenterX, rotateCenterY] = this.getIconCenter(this.rotateIcon.coordinate);
        const rotateVector = [rotateCenterX - this.center[0], rotateCenterY - this.center[1]];
        const rotateVectorNew = [rotateVector[0] * scale, rotateVector[1] * scale];
        const rotateIconCenter = [rotateVectorNew[0] + this.center[0], rotateVectorNew[1] + this.center[1]];
        this.rotateIcon.coordinate = this.getIconCoordinateByIconCenter(rotateIconCenter);
       
       

        // 右上角缩放按钮
        const [scaleCenterX, scaleCenterY] = this.getIconCenter(this.scaleIcon.coordinate);
        const scaleVector = [scaleCenterX - this.center[0], scaleCenterY - this.center[1]];
        const scaleVectorNew = [scaleVector[0] * scale, scaleVector[1] * scale];
        const scaleIconCenter = [scaleVectorNew[0] + this.center[0], scaleVectorNew[1] + this.center[1]];
        this.scaleIcon.coordinate = this.getIconCoordinateByIconCenter(scaleIconCenter);


        // 左下角删除按钮
        const [delCenterX, delCenterY] = this.getIconCenter(this.delIcon.coordinate);
        const delVector = [delCenterX - this.center[0], delCenterY- this.center[1]];
        const delVectorNew = [delVector[0] * scale, delVector[1] * scale];
        const delIconCenter = [delVectorNew[0] + this.center[0], delVectorNew[1] + this.center[1]];
        this.delIcon.coordinate = this.getIconCoordinateByIconCenter(delIconCenter);

        this.updateItemCoordinateByScale(this, this.center, scale);

        this.baseSize = this.size.slice(0);
        
        
    }

    updateItemCoordinateByRotate(target, center, angle){
        const [centerX, centerY] = center;
        const coordinateVector = target.coordinate.map( point => {
            return [point[0]- centerX, point[1] - centerY];
        });
        const newCoordinateVector = coordinateVector.map( vector => {
            const [x, y] = vector;
            // x2 = x1 * cos - y1 * sin;
            // y2 = x1 * sin + y1 * cos;
            const newX = x * Math.cos(angle) - y * Math.sin(angle);
            const newY = x * Math.sin(angle) + y * Math.cos(angle);
            return [newX, newY];
        });
        target.coordinate = newCoordinateVector.map( vector => {
            return [vector[0] + centerX , vector[1] + centerY];
        });
    }

    draw(ctx) {

        const sprite = this;
        ctx.save();
        const [x, y] = sprite.pos;
        const [width, height] = sprite.size;
        ctx.beginPath();

        if (this.rotateAngle !== 0) {
            const centerX = x + width / 2;
            const centerY = y + height / 2;
            ctx.translate(centerX, centerY);
            ctx.rotate(this.rotateAngle);
            ctx.translate(-centerX, -centerY);
        }

        ctx.rect(x, y, width, height);
        ctx.fillStyle = '#f00';
        ctx.fill();
        ctx.restore();
        this.drawIcon(ctx, sprite.delIcon);
        this.drawIcon(ctx, sprite.rotateIcon);
        this.drawIcon(ctx, sprite.scaleIcon);

    }

    drawIcon(ctx, icon) {
        ctx.beginPath();
        ctx.save();
        const [x, y] = icon.pos;
        const [width, height] = icon.size;
        
        if (this.rotateAngle !== 0) {
            const [spriteX, spriteY] = this.pos;
            const [spriteW, spriteH] = this.size;
            const centerX = spriteX + spriteW / 2;
            const centerY = spriteY + spriteH / 2;
            ctx.translate(centerX, centerY);
            ctx.rotate(this.rotateAngle);
            ctx.translate(-centerX, -centerY);
        }

        if (icon.self) {
            ctx.drawImage(icon.self, x, y, width, height);
        } else {
            const img = new Image();
            img.crossOrigin = "anonymous";
            img.src = icon.url;
            img.onload = function () {
                icon.self = img;
                ctx.drawImage(img, x, y, width, height);
            }
        }
        ctx.restore();
    }

    init() {
        this.initDelIcon();
        this.initRotateIcon();
        this.initScaleIcon();
    }

    // 删除按钮,左下角
    initDelIcon() {
        const [width, height] = this.size;
        const [x, y] = this.pos;
        this.delIcon = { 
            ...this.delIcon,
            pos: [x - BOX_PADDING - ICON_HEIGHT * 0.5, y + height + BOX_PADDING - ICON_HEIGHT * 0.5],
            size: [ICON_HEIGHT, ICON_HEIGHT],
            url: DEL_ICON,
            parent: this
        };
        this.delIcon.coordinate = this.setCoordinate(this.delIcon.pos, this.delIcon.size);
    }

    // 缩放按钮,右上角
    initScaleIcon() {
        const [width, height] = this.size;
        const [x, y] = this.pos;
        this.scaleIcon = { 
            ...this.scaleIcon,
            pos: [x + width + BOX_PADDING - ICON_HEIGHT * 0.5, y - BOX_PADDING - ICON_HEIGHT * 0.5],
            size: [ICON_HEIGHT, ICON_HEIGHT],
            url: SCALE_ICON,
            parent: this
        };
        
        this.scaleIcon.coordinate = this.setCoordinate(this.scaleIcon.pos, this.scaleIcon.size);
    }

    // 旋转按钮,左上角
    initRotateIcon() {
        const [width, height] = this.size;
        const [x, y] = this.pos;
        this.rotateIcon = { 
            ...this.rotateIcon,
            pos: [x - BOX_PADDING - ICON_HEIGHT * 0.5, y - BOX_PADDING - ICON_HEIGHT * 0.5],
            size: [ICON_HEIGHT, ICON_HEIGHT],
            url: ROTATE_ICON,
            parent: this
        };
        // const point = this.coordinate[0];
        // this.rotateIcon.coordinate = this.setIconCoordinate([point[0] - BOX_PADDING, point[1] - BOX_PADDING]);
        this.rotateIcon.coordinate = this.setCoordinate(this.rotateIcon.pos, this.rotateIcon.size);
    }

    // 重置icon的位置与大小
    resetIconPos() {
        const [width, height] = this.size;
        const [x, y] = this.pos;
        this.rotateIcon = { 
            ...this.rotateIcon,
            pos: [x - BOX_PADDING - ICON_HEIGHT * 0.5, y - BOX_PADDING - ICON_HEIGHT * 0.5],
            size: [ICON_HEIGHT, ICON_HEIGHT]
        };
        this.scaleIcon = {
            ...this.scaleIcon,
            pos: [x + width + BOX_PADDING - ICON_HEIGHT * 0.5, y - BOX_PADDING - ICON_HEIGHT * 0.5],
            size: [ICON_HEIGHT, ICON_HEIGHT]
        };
        this.delIcon = {
            ...this.delIcon,
            pos: [x - BOX_PADDING - ICON_HEIGHT * 0.5, y + height + BOX_PADDING - ICON_HEIGHT * 0.5],
            size: [ICON_HEIGHT, ICON_HEIGHT]
        };
    }

    resetPos(dirX, dirY) {
        const [oX, oY] = this.pos;
        this.pos = [oX + dirX, oY + dirY];
        this.center = [this.center[0] + dirX, this.center[1] + dirY];

        // 更新四个顶点的坐标
        this.coordinate = this.coordinate.map(point => {
            return [point[0] + dirX, point[1] + dirY];
        });

        if (this.delIcon) {
            const [x, y] = this.delIcon.pos;
            this.delIcon.pos = [x + dirX, y + dirY];
            this.delIcon.coordinate = this.delIcon.coordinate.map(point => {
                return [point[0] + dirX, point[1] + dirY];
            });
        }
        if (this.scaleIcon) {
            const [x, y] = this.scaleIcon.pos;
            this.scaleIcon.pos = [x + dirX, y + dirY];
            this.scaleIcon.coordinate = this.scaleIcon.coordinate.map(point => {
                return [point[0] + dirX, point[1] + dirY];
            });
        }
        if (this.rotateIcon) {
            const [x, y] = this.rotateIcon.pos;
            this.rotateIcon.pos = [x + dirX, y + dirY];
            this.rotateIcon.coordinate = this.rotateIcon.coordinate.map(point => {
                return [point[0] + dirX, point[1] + dirY];
            });
        }
    }

    resetSize(dir) {
        const sprite = this;
        const [oWidth, oHeight] = sprite.size;
        this.scalePercent = (oWidth + dir) / oWidth; // 使用x轴的长度来确定缩放的比例
        const [minWidth, minHeight] = sprite.minSize;
        const [maxWidth, maxHeight] = sprite.maxSize;
        const [centerX, centerY] = sprite.center;

        let width = oWidth * this.scalePercent;
        let height = oHeight * this.scalePercent;
        width < minWidth && (width = minWidth);
        height < minHeight && (height = minHeight);
        width > maxWidth && (width = maxWidth);
        height > maxHeight && (height = maxHeight);

        const x = centerX - width / 2;
        const y = centerY - height / 2;
        sprite.pos = [x, y];
        sprite.size = [width, height];

        sprite.resetIconPos();
    }

    setRotateAngle(angleDir) {
        this.rotateAngle += angleDir;
        this.rotateAngleDir += angleDir;
    }

}

window.onload = function () {

    const stage = new Stage({
        canvas: document.querySelector('canvas')
    });

    document.querySelector('.red-box').addEventListener('click', function () {
        const randomX = Math.floor(Math.random() * 200);
        const randomY = Math.floor(Math.random() * 200);
        const sprite = new Sprite({
            pos: [randomX, randomY],
            size: [120, 60],
            minSize: [40, 20],
            maxSize: [240, 120]
        });
        stage.append(sprite);
    });

}

</script>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容