// 图片展示扩展 - 慕然科技的图片展示
// 扩展ID: photo
//慕然科技官网ccwmoran.github.io
class PhotoDisplayExtension {
constructor() {
this.images = {};
this.nextZIndex = 1000;
// 形状选项
this.shapeOptions = [
'原图',
'圆形',
'正方形',
'圆角矩形',
'椭圆形'
];
}
getInfo() {
return {
id: 'photo',
name: '科技创新的图片展示',
color1: '#4A90E2',
color2: '#357ABD',
blocks: [
{
opcode: 'showImage',
blockType: Scratch.BlockType.COMMAND,
text: '显示图片 名称 [NAME] 地址 [URL] 位置 X [X] Y [Y] 大小 [SIZE] 形状 [SHAPE]',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
},
URL: {
type: Scratch.ArgumentType.STRING,
defaultValue: 'https://example.com/image.jpg'
},
X: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 100
},
Y: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 100
},
SIZE: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 150
},
SHAPE: {
type: Scratch.ArgumentType.STRING,
menu: 'SHAPE_MENU'
}
}
},
{
opcode: 'removeImage',
blockType: Scratch.BlockType.COMMAND,
text: '删除图片 [NAME]',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
}
}
},
{
opcode: 'moveImage',
blockType: Scratch.BlockType.COMMAND,
text: '移动图片 [NAME] 到 X [X] Y [Y]',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
},
X: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 200
},
Y: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 200
}
}
},
{
opcode: 'resizeImage',
blockType: Scratch.BlockType.COMMAND,
text: '设置图片 [NAME] 大小为 [SIZE]',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
},
SIZE: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 200
}
}
},
{
opcode: 'changeImageShape',
blockType: Scratch.BlockType.COMMAND,
text: '设置图片 [NAME] 形状为 [SHAPE]',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
},
SHAPE: {
type: Scratch.ArgumentType.STRING,
menu: 'SHAPE_MENU'
}
}
},
{
opcode: 'setImageOpacity',
blockType: Scratch.BlockType.COMMAND,
text: '设置图片 [NAME] 透明度 [OPACITY]%',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
},
OPACITY: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 100
}
}
},
{
opcode: 'bringToFront',
blockType: Scratch.BlockType.COMMAND,
text: '将图片 [NAME] 置于顶层',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
}
}
},
{
opcode: 'sendToBack',
blockType: Scratch.BlockType.COMMAND,
text: '将图片 [NAME] 置于底层',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
}
}
},
{
opcode: 'rotateImage',
blockType: Scratch.BlockType.COMMAND,
text: '旋转图片 [NAME] [DEGREES] 度',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
},
DEGREES: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 45
}
}
},
{
opcode: 'flipImage',
blockType: Scratch.BlockType.COMMAND,
text: '翻转图片 [NAME] [DIRECTION]',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
},
DIRECTION: {
type: Scratch.ArgumentType.STRING,
menu: 'FLIP_MENU'
}
}
},
{
opcode: 'whenImageClicked',
blockType: Scratch.BlockType.HAT,
text: '当图片 [NAME] 被点击',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
}
}
},
{
opcode: 'getImageX',
blockType: Scratch.BlockType.REPORTER,
text: '图片 [NAME] 的X坐标',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
}
}
},
{
opcode: 'getImageY',
blockType: Scratch.BlockType.REPORTER,
text: '图片 [NAME] 的Y坐标',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
}
}
},
{
opcode: 'getImageCount',
blockType: Scratch.BlockType.REPORTER,
text: '图片数量'
},
{
opcode: 'imageExists',
blockType: Scratch.BlockType.BOOLEAN,
text: '图片 [NAME] 存在?',
arguments: {
NAME: {
type: Scratch.ArgumentType.STRING,
defaultValue: '图片1'
}
}
}
],
menus: {
SHAPE_MENU: {
items: this.shapeOptions
},
FLIP_MENU: {
items: ['水平翻转', '垂直翻转']
}
},
docsURI: 'https://kejicx.github.io/doc/'
};
}
// 显示图片
showImage(args) {
const { NAME, URL, X, Y, SIZE, SHAPE } = args;
// 如果图片已存在,先移除
if (this.images[NAME]) {
this.removeImage({ NAME });
}
// 创建图片元素
const img = new Image();
img.onload = () => {
this._applyImageStyles(img, X, Y, SIZE, SHAPE);
};
img.onerror = () => {
console.error(`无法加载图片: ${URL}`);
};
img.src = URL;
img.dataset.name = NAME;
img.dataset.loaded = 'false';
// 添加点击事件
img.addEventListener('click', (event) => {
event.stopPropagation();
this._handleImageClick(NAME);
});
// 添加到舞台
const stage = this._getStage();
if (stage) {
stage.appendChild(img);
} else {
console.warn('无法找到舞台元素');
document.body.appendChild(img);
}
// 存储图片信息
this.images[NAME] = {
element: img,
url: URL,
x: X,
y: Y,
size: SIZE,
shape: SHAPE,
opacity: 100,
rotation: 0,
zIndex: this.nextZIndex++
};
}
// 删除图片
removeImage(args) {
const { NAME } = args;
if (this.images[NAME]) {
const img = this.images[NAME].element;
if (img.parentNode) {
img.parentNode.removeChild(img);
}
delete this.images[NAME];
}
}
// 移动图片
moveImage(args) {
const { NAME, X, Y } = args;
if (this.images[NAME]) {
const img = this.images[NAME].element;
img.style.left = X + 'px';
img.style.top = Y + 'px';
this.images[NAME].x = X;
this.images[NAME].y = Y;
}
}
// 调整图片大小
resizeImage(args) {
const { NAME, SIZE } = args;
if (this.images[NAME]) {
const img = this.images[NAME].element;
img.style.width = SIZE + 'px';
img.style.height = SIZE + 'px';
this.images[NAME].size = SIZE;
// 重新应用形状
this._applyShape(img, this.images[NAME].shape);
}
}
// 更改图片形状
changeImageShape(args) {
const { NAME, SHAPE } = args;
if (this.images[NAME]) {
const img = this.images[NAME].element;
this._applyShape(img, SHAPE);
this.images[NAME].shape = SHAPE;
}
}
// 设置透明度
setImageOpacity(args) {
const { NAME, OPACITY } = args;
if (this.images[NAME]) {
const img = this.images[NAME].element;
const opacityValue = Math.max(0, Math.min(100, OPACITY)) / 100;
img.style.opacity = opacityValue;
this.images[NAME].opacity = OPACITY;
}
}
// 置于顶层
bringToFront(args) {
const { NAME } = args;
if (this.images[NAME]) {
const img = this.images[NAME].element;
img.style.zIndex = this.nextZIndex++;
this.images[NAME].zIndex = img.style.zIndex;
}
}
// 置于底层
sendToBack(args) {
const { NAME } = args;
if (this.images[NAME]) {
const img = this.images[NAME].element;
img.style.zIndex = 0;
this.images[NAME].zIndex = 0;
}
}
// 旋转图片
rotateImage(args) {
const { NAME, DEGREES } = args;
if (this.images[NAME]) {
const img = this.images[NAME].element;
img.style.transform = `rotate(${DEGREES}deg)`;
this.images[NAME].rotation = DEGREES;
}
}
// 翻转图片
flipImage(args) {
const { NAME, DIRECTION } = args;
if (this.images[NAME]) {
const img = this.images[NAME].element;
let currentTransform = img.style.transform || '';
// 移除现有的翻转样式
currentTransform = currentTransform.replace(/scaleX\([^)]*\)/g, '');
currentTransform = currentTransform.replace(/scaleY\([^)]*\)/g, '');
if (DIRECTION === '水平翻转') {
currentTransform += ' scaleX(-1)';
} else if (DIRECTION === '垂直翻转') {
currentTransform += ' scaleY(-1)';
}
img.style.transform = currentTransform.trim();
}
}
// 图片点击事件处理
whenImageClicked(args) {
// 这个函数由Scratch运行时调用,当图片被点击时触发相应的事件
// 实际实现中,这里会返回一个布尔值来触发帽子积木
return false;
}
// 获取图片X坐标
getImageX(args) {
const { NAME } = args;
return this.images[NAME] ? this.images[NAME].x : 0;
}
// 获取图片Y坐标
getImageY(args) {
const { NAME } = args;
return this.images[NAME] ? this.images[NAME].y : 0;
}
// 获取图片数量
getImageCount() {
return Object.keys(this.images).length;
}
// 检查图片是否存在
imageExists(args) {
const { NAME } = args;
return !!this.images[NAME];
}
// 内部方法:应用图片样式
_applyImageStyles(img, x, y, size, shape) {
img.style.position = 'absolute';
img.style.left = x + 'px';
img.style.top = y + 'px';
img.style.width = size + 'px';
img.style.height = size + 'px';
img.style.zIndex = this.nextZIndex++;
img.style.cursor = 'pointer';
img.style.userSelect = 'none';
img.dataset.loaded = 'true';
this._applyShape(img, shape);
}
// 内部方法:应用形状
_applyShape(img, shape) {
// 重置样式
img.style.borderRadius = '0';
img.style.objectFit = 'cover';
img.style.clipPath = 'none';
switch(shape) {
case '圆形':
img.style.borderRadius = '50%';
break;
case '正方形':
img.style.borderRadius = '0';
break;
case '原图':
img.style.objectFit = 'contain';
break;
case '圆角矩形':
img.style.borderRadius = '15%';
break;
case '椭圆形':
img.style.borderRadius = '50% / 25%';
break;
}
}
// 内部方法:获取舞台元素
_getStage() {
// 尝试不同的选择器来找到舞台
const selectors = [
'#stage',
'.stage',
'[class*="stage"]',
'.gui_stage'
];
for (const selector of selectors) {
const element = document.querySelector(selector);
if (element) {
return element;
}
}
return document.body;
}
// 内部方法:处理图片点击
_handleImageClick(name) {
// 这里可以添加自定义的点击处理逻辑
console.log(`图片 ${name} 被点击`);
// 触发Scratch事件(如果需要)
// 实际实现中,这里会与Scratch运行时交互来触发帽子积木
}
// 清理函数
_shutdown() {
// 移除所有图片
for (const name in this.images) {
if (this.images.hasOwnProperty(name)) {
this.removeImage({ NAME: name });
}
}
this.images = {};
}
}
// 注册扩展
Scratch.extensions.register(new PhotoDisplayExtension());