事件替换颜色等画布

This commit is contained in:
李志鹏
2026-01-09 14:40:18 +08:00
parent 274ea15dcc
commit 88bd58fc66
8 changed files with 584 additions and 200 deletions

View File

@@ -977,6 +977,17 @@ defineExpose({
...opts,
});
},
updateOtherLayers: async (otherData) => {
await canvasManager?.createOtherLayers?.(otherData, true);
layerManager.activeLayerId.value = ""
layerManager?.sortLayers();
await layerManager?.updateLayersObjectsInteractivity?.(true);
canvasManager?.canvas?.renderAll();
setTimeout(() => {
canvasManager.updateAllThumbnails();
}, 500);
return true;
},
//图片url或者base64
addImageToLayer: async (
url,

View File

@@ -1325,12 +1325,12 @@ export class CanvasManager {
// }
// 重载代码后支持回调中操作一些内容
await calllBack?.();
// 确保所有对象的交互性正确设置
await this.layerManager?.updateLayersObjectsInteractivity?.();
console.log(this.layerManager.layers.value);
await calllBack?.();
// 更新所有缩略图
setTimeout(() => {
this.updateAllThumbnails();
@@ -1357,13 +1357,22 @@ export class CanvasManager {
* 创建其他图层:印花、颜色、元素...
* @param {Object} otherData - 其他图层数据
*/
async createOtherLayers(otherData) {
async createOtherLayers(otherData, isUpdate = false) {
if (!otherData) return console.warn("otherData 为空不需要添加");
const otherData_ = JSON.parse(JSON.stringify(otherData));
console.log("==========创建其他图层", otherData_);
const updateColor = !!otherData_.color;
const updateSpecialGroup = !!otherData_.printObject || !!otherData_.trims;
// 删除颜色图层和特殊组图层
const ids = [SpecialLayerId.COLOR, SpecialLayerId.SPECIAL_GROUP];
const ids = [];
if(isUpdate){
updateColor && ids.push(SpecialLayerId.COLOR)
updateSpecialGroup && ids.push(SpecialLayerId.SPECIAL_GROUP)
}else{
ids.push(SpecialLayerId.COLOR)
ids.push(SpecialLayerId.SPECIAL_GROUP)
}
this.layers.value = this.layers.value.filter((layer) => {
if(ids.includes(layer.id)){
ids.push(...layer.children?.map((child) => child.id));
@@ -1375,11 +1384,11 @@ export class CanvasManager {
// 创建颜色图层
await this.createColorLayer(otherData_.color);
otherData_.color && await this.createColorLayer(otherData_.color);
const printTrimsLayers = [];// 印花和元素图层
const singleLayers = [];// 平铺图层
otherData_?.printObject?.prints?.forEach((print, index) => {
otherData_.printObject?.prints?.forEach((print, index) => {
print.name = t("Canvas.Print") + (index + 1);
if(print.ifSingle){
printTrimsLayers.unshift({...print});
@@ -1387,12 +1396,13 @@ export class CanvasManager {
singleLayers.unshift({...print});
}
})
otherData_?.trims?.prints?.forEach((trims, index) => {
otherData_.trims?.prints?.forEach((trims, index) => {
trims.name = t("Canvas.Elements") + (index + 1);
printTrimsLayers.unshift({...trims});
})
await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
if(isUpdate ? updateSpecialGroup : true){
await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
}
await this.changeCanvas();
}
@@ -1432,8 +1442,8 @@ export class CanvasManager {
});
tagObject.set('clipPath', transparentMask);
}
async createColorLayer(color){
if(!color) return console.warn("颜色为空不需要添加");
async createColorLayer(color_){
const color = color_ || {r:0,g:0,b:0,a:0};
// if(findLayer(this.layers.value, SpecialLayerId.COLOR)) {
// return console.warn("画布中已存在颜色图层");
// }
@@ -1619,7 +1629,7 @@ export class CanvasManager {
// })
// children.push(layer);
// }
if(children.length === 0) return;
// if(children.length === 0) return;
const groupRect = new fabric.Rect({});
await this.setObjecCliptInfo(groupRect);
// 插入组图层
@@ -1638,6 +1648,8 @@ export class CanvasManager {
specialType: SpecialType.PRINT_TRIMS_G,
});
this.layers.value.splice(groupIndex, 0, groupLayer);
console.log("==========layers", [...this.layers.value]);
}
/**

View File

@@ -199,9 +199,12 @@ export class LayerManager {
if (!this.canvas) return;
if (isUseOptimize) {
// 优化渲染 - 统一批处理 支持异步回调
await optimizeCanvasRendering(this.canvas, async () => {
// 应用图层交互规则
await this._applyInteractionRules({ isMoveing });
await new Promise((resolve) => {
optimizeCanvasRendering(this.canvas, async () => {
// 应用图层交互规则
await this._applyInteractionRules({ isMoveing });
resolve();
});
});
} else {
// 直接应用图层交互规则
@@ -333,7 +336,6 @@ export class LayerManager {
const objects = this.canvas.getObjects();
const editorMode = this.editorMode || CanvasConfig.defaultTool;
const layers = this.layers?.value || [];
// 创建缓存以避免重复查找
const layerMap = {};
layers.forEach((layer) => {

View File

@@ -109,7 +109,32 @@ const exportExtraInfo = async () => {
}
};
// 更新其他图层颜色
const updateOtherLayersColor = async () => {
const obj = {
color: {rgba: {r:255,g:255,b:0,a:1}},
}
await canvasEditor?.value?.updateOtherLayers?.(obj);
};
// 更新其他图层印花
const updateOtherLayersPrint = async () => {
const obj = {
printObject: {
prints: [
{
ifSingle: true,
level2Type: "Pattern",
designType: "Library",
path: "/src/assets/images/canvas/yinhua1.jpg",
location: [250, 780],
scale: [0.3, 0.4],
angle: 0,
},
]
},
}
await canvasEditor?.value?.updateOtherLayers?.(obj);
};
const changeCanvas = (command) => {
console.log(command);
@@ -219,6 +244,20 @@ const customToolsList = ref([
label: "导E",
class: "export-btn",
},
{
id: "updateExtraInfo_color",
title: "更新颜色",
action: updateOtherLayersColor,
label: "更C",
class: "export-btn",
},
{
id: "updateExtraInfo_print",
title: "更新印花",
action: updateOtherLayersPrint,
label: "更P",
class: "export-btn",
},
{
id: "exportPNG",
title: "导出PNG", //导出画布图片
@@ -309,7 +348,7 @@ const otherData = {
level2Type: "Pattern",
designType: "Library",
path: "/src/assets/images/canvas/yinhua1.jpg",
location: [650, 650],
location: [550, 650],
scale: [0.15, 0.2],
angle: 0,
},

View File

@@ -0,0 +1,156 @@
<template>
<div class="pingpu" ref="el"><canvas ref="canvasRef"></canvas></div>
</template>
<script setup>
import { fabric } from "fabric-with-all";
import { ref, watch, onMounted } from "vue";
const props = defineProps({
url: { type: String, required: true },
offsetX: { type: Number, default: 0 }, // px
offsetY: { type: Number, default: 0 }, // px
angle: { type: Number, default: 0 }, // 角度
scale: { type: Number, default: 100 }, // %
gapX: { type: Number, default: 0 }, // px
gapY: { type: Number, default: 0 }, // px
});
watch(
() => props.url,
() => getOriginalImage()
);
watch(
() => [
props.offsetX,
props.offsetY,
props.angle,
props.scale,
props.gapX,
props.gapY,
],
() => setCanvasData()
);
const el = ref(null);
const canvasRef = ref(null);
const canvas = ref(null);
const observer = ref(null);
const id = "asfs123121sfe";
onMounted(async () => {
initCanvas();
await getOriginalImage();
setCanvasData();
let throttleTimeout = null;
let lastRunTime = 0;
let trailingTimeout = null;
observer.value = new ResizeObserver((entries) => {
const now = Date.now();
const throttleDelay = 100;
if (!throttleTimeout) {
updateCanvasSize();
lastRunTime = now;
throttleTimeout = setTimeout(() => {
throttleTimeout = null;
}, throttleDelay);
} else {
clearTimeout(trailingTimeout);
trailingTimeout = setTimeout(() => {
updateCanvasSize();
lastRunTime = Date.now();
}, throttleDelay);
}
});
observer.value.observe(el.value);
});
onBeforeUnmount(() => {
observer.value.disconnect();
});
const initCanvas = () => {
canvas.value = new fabric.Canvas(canvasRef.value, {
selection: false,
evented: false,
});
canvas.value.setWidth(el.value.offsetWidth);
canvas.value.setHeight(el.value.offsetHeight);
};
const updateCanvasSize = () => {
canvas.value.setWidth(el.value.offsetWidth);
canvas.value.setHeight(el.value.offsetHeight);
setCanvasData();
};
const originalImage = ref(null);
const getOriginalImage = () => {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(
props.url,
(object) => {
const imgElement = object.getElement();
// 创建透明 Canvas
const tcanvas = document.createElement("canvas");
tcanvas.width = imgElement.width;
tcanvas.height = imgElement.height;
const ctx = tcanvas.getContext("2d");
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(imgElement, 0, 0);
originalImage.value = tcanvas;
resolve(tcanvas);
},
{ crossOrigin: "anonymous" }
);
});
};
const setCanvasData = () => {
canvas.value.getObjects().forEach((obj) => {
if (obj.id === id) canvas.value.remove(obj);
});
const image = originalImage.value;
const cwidth = canvas.value.width;
const cheight = canvas.value.height;
const offsetX = props.offsetX;
const offsetY = props.offsetY;
const scaleX = ((cwidth / image.width) * (props.scale / 100)) / 5;
const scaleY = ((cheight / image.height) * (props.scale / 100)) / 5;
const scale = cwidth > cheight ? scaleX : scaleY;
const angle = props.angle;
const gapX = props.gapX;
const gapY = props.gapY;
const patternTransform = fabric.util.composeMatrix({
scaleX: scale,
scaleY: scale,
angle: angle,
});
// 创建透明 Canvas
const tcanvas = document.createElement("canvas");
tcanvas.width = image.width + gapX;
tcanvas.height = image.height + gapY;
const ctx = tcanvas.getContext("2d");
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(image, 0, 0);
const pattern = new fabric.Pattern({
source: tcanvas,
repeat: "repeat",
patternTransform,
offsetX, // 水平偏移
offsetY, // 垂直偏移
});
const rect = new fabric.Rect({
id,
width: cwidth,
height: cheight,
top: 0,
left: 0,
// scaleX: 1,
// scaleY: 1,
fill: pattern,
// evented: false,
// selectable: false,
});
canvas.value.add(rect);
canvas.value.renderAll();
};
</script>
<style lang='less' scoped>
.pingpu {
width: 100%;
height: 100%;
}
</style>

View File

@@ -6,38 +6,16 @@
import { fabric } from "fabric-with-all";
import { ref, watch, onMounted } from "vue";
const props = defineProps({
url: { type: String, required: true },
offsetX: { type: Number, default: 0 }, // px
offsetY: { type: Number, default: 0 }, // px
angle: { type: Number, default: 0 }, // 角度
scale: { type: Number, default: 100 }, // %
gapX: { type: Number, default: 0 }, // px
gapY: { type: Number, default: 0 }, // px
list: { type: Array, default: () => [] },
});
watch(
() => props.url,
() => getOriginalImage()
);
watch(
() => [
props.offsetX,
props.offsetY,
props.angle,
props.scale,
props.gapX,
props.gapY,
],
() => setCanvasData()
);
const el = ref(null);
const canvasRef = ref(null);
const canvas = ref(null);
const observer = ref(null);
const id = "asfs123121sfe";
var canvas = null;
onMounted(async () => {
initCanvas();
await getOriginalImage();
setCanvasData();
await setCanvasData();
let throttleTimeout = null;
let lastRunTime = 0;
let trailingTimeout = null;
@@ -64,23 +42,21 @@
observer.value.disconnect();
});
const initCanvas = () => {
canvas.value = new fabric.Canvas(canvasRef.value, {
canvas = new fabric.Canvas(canvasRef.value, {
selection: false,
evented: false,
});
canvas.value.setWidth(el.value.offsetWidth);
canvas.value.setHeight(el.value.offsetHeight);
canvas.setWidth(el.value.offsetWidth);
canvas.setHeight(el.value.offsetHeight);
};
const updateCanvasSize = () => {
canvas.value.setWidth(el.value.offsetWidth);
canvas.value.setHeight(el.value.offsetHeight);
setCanvasData();
const updateCanvasSize = async () => {
canvas.setWidth(el.value.offsetWidth);
canvas.setHeight(el.value.offsetHeight);
await setCanvasData();
};
const originalImage = ref(null);
const getOriginalImage = () => {
const urlToCanvas = (url) => {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(
props.url,
url,
(object) => {
const imgElement = object.getElement();
// 创建透明 Canvas
@@ -90,62 +66,66 @@
const ctx = tcanvas.getContext("2d");
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(imgElement, 0, 0);
originalImage.value = tcanvas;
resolve(tcanvas);
},
{ crossOrigin: "anonymous" }
);
});
};
const setCanvasData = () => {
canvas.value.getObjects().forEach((obj) => {
if (obj.id === id) canvas.value.remove(obj);
});
const image = originalImage.value;
const cwidth = canvas.value.width;
const cheight = canvas.value.height;
const offsetX = props.offsetX;
const offsetY = props.offsetY;
const scaleX = ((cwidth / image.width) * (props.scale / 100)) / 5;
const scaleY = ((cheight / image.height) * (props.scale / 100)) / 5;
const scale = cwidth > cheight ? scaleX : scaleY;
const angle = props.angle;
const gapX = props.gapX;
const gapY = props.gapY;
const patternTransform = fabric.util.composeMatrix({
scaleX: scale,
scaleY: scale,
angle: angle,
});
// 创建透明 Canvas
const tcanvas = document.createElement("canvas");
tcanvas.width = image.width + gapX;
tcanvas.height = image.height + gapY;
const ctx = tcanvas.getContext("2d");
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(image, 0, 0);
const pattern = new fabric.Pattern({
source: tcanvas,
repeat: "repeat",
patternTransform,
offsetX, // 水平偏移
offsetY, // 垂直偏移
});
const rect = new fabric.Rect({
id,
width: cwidth,
height: cheight,
top: 0,
left: 0,
// scaleX: 1,
// scaleY: 1,
fill: pattern,
evented: false,
selectable: false,
});
canvas.value.add(rect);
canvas.value.renderAll();
const setCanvasData = async () => {
canvas.clear();
const cwidth = canvas.width;
const cheight = canvas.height;
for (let i = 0; i < props.list.length; i++) {
let item = props.list[i];
let image = await urlToCanvas(item.path);
let offsetX = item.location[0];
let offsetY = item.location[1];
let scaleX = ((cwidth / image.width) * item.scale[0]) / 5;
let scaleY = ((cheight / image.height) * item.scale[1]) / 5;
let scale = cwidth > cheight ? scaleX : scaleY;
let angle = item.angle;
let gapX = item.object.gapX;
let gapY = item.object.gapY;
let patternTransform = fabric.util.composeMatrix({
scaleX: scale,
scaleY: scale,
angle: angle,
});
// 创建透明 Canvas
let tcanvas = document.createElement("canvas");
tcanvas.width = image.width + gapX;
tcanvas.height = image.height + gapY;
let ctx = tcanvas.getContext("2d");
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(image, 0, 0);
let pattern = new fabric.Pattern({
source: tcanvas,
repeat: "repeat",
patternTransform,
offsetX, // 水平偏移
offsetY, // 垂直偏移
});
let rect = new fabric.Rect({
id: item.id,
width: cwidth,
height: cheight,
fill: pattern,
...item.object,
});
canvas.add(rect);
}
canvas.renderAll();
};
const updataList = (list) => {
console.log(list);
};
defineExpose({
updataList,
});
</script>
<style lang='less' scoped>

View File

@@ -0,0 +1,104 @@
<template>
<div class="test">
<!-- <div class="control" v-for="(item, index) in list" :key="index">
<div>
<span>偏移X</span>
<input type="number" v-model="item.offsetX" />
</div>
<div>
<span>偏移Y</span>
<input type="number" v-model="item.offsetY" />
</div>
<div>
<span>角度</span>
<input type="number" v-model="item.angle" />
</div>
<div>
<span>缩放</span>
<input type="number" v-model="item.scale" />
</div>
<div>
<span>水平间距</span>
<input type="number" v-model="item.gapX" />
</div>
<div>
<span>垂直间距</span>
<input type="number" v-model="item.gapY" />
</div>
<h2>---------整体--------</h2>
<div>
<span>缩放</span>
<input type="number" v-model="item.object.scale" step="0.1" />
</div>
<div>
<span>X</span>
<input type="number" v-model="item.object.left" />
</div>
<div>
<span>Y</span>
<input type="number" v-model="item.object.top" />
</div>
</div> -->
<div class="box">
<pingpu :list="list" />
</div>
</div>
</template>
<script lang="ts" setup>
import pingpu from "./pingpu.vue";
const list = ref([
{
ifSingle: false,
level2Type: "Pattern",
designType: "Library",
path: "/src/assets/images/canvas/yinhua1.jpg",
location: [0, 0],
scale: [1, 1],
angle: 0,
name: "Print2",
priority: 1,
object: {
top: 0,
left: 0,
scaleX: 1,
scaleY: 1,
opacity: 1,
angle: 0,
flipX: false,
flipY: false,
blendMode: "multiply",
gapX: 0,
gapY: 0,
},
gap: [0, 0],
},
]);
</script>
<style lang='less' scoped>
.test {
> .control {
margin-left: 10px;
margin-top: 10px;
> div {
display: flex;
align-items: center;
margin-bottom: 5px;
> span {
width: 80px;
}
> input {
padding-left: 10px;
border-radius: 5px;
}
}
}
> .box {
width: 300px;
height: 500px;
overflow: hidden;
border: 1px solid #000;
}
}
</style>

View File

@@ -1,121 +1,201 @@
<template>
<div class="test">
<div v-for="(item, index) in data" :key="index">
<div class="control">
<div>
<span>偏移X</span>
<input type="number" v-model="item.offsetX" />
</div>
<div>
<span>偏移Y</span>
<input type="number" v-model="item.offsetY" />
</div>
<div>
<span>角度</span>
<input type="number" v-model="item.angle" />
</div>
<div>
<span>缩放</span>
<input type="number" v-model="item.scale" />
</div>
<div>
<span>水平间距</span>
<input type="number" v-model="item.gapX" />
</div>
<div>
<span>垂直间距</span>
<input type="number" v-model="item.gapY" />
</div>
<h2>---------整体--------</h2>
<div>
<span>缩放</span>
<input type="number" v-model="item.object.scale" step="0.1" />
</div>
<div>
<span>X</span>
<input type="number" v-model="item.object.left" />
</div>
<div>
<span>Y</span>
<input type="number" v-model="item.object.top" />
</div>
<div class="control" v-for="(item, index) in list" :key="index">
<div>
<span>偏移X</span>
<input type="number" v-model="item.location[0]" />
</div>
<div class="box">
<pingpu v-bind="item" :style="{
transform: `translate(${item.object.left}px, ${item.object.top}px) scale(${item.object.scale})`,
transformOrigin: 'top left'
}" />
<div>
<span>偏移Y</span>
<input type="number" v-model="item.location[1]" />
</div>
<div>
<span>角度</span>
<input type="number" v-model="item.angle" />
</div>
<div>
<span>缩放</span>
<input type="number" v-model="item.scale" />
</div>
<div>
<span>水平间距</span>
<input type="number" v-model="item.object.gapX" />
</div>
<div>
<span>垂直间距</span>
<input type="number" v-model="item.object.gapY" />
</div>
<hr />
<div>
<span>缩放X</span>
<input type="number" v-model="item.object.scaleX" step="0.1" />
</div>
<div>
<span>缩放Y</span>
<input type="number" v-model="item.object.scaleY" step="0.1" />
</div>
<div>
<span>X</span>
<input type="number" v-model="item.object.left" />
</div>
<div>
<span>Y</span>
<input type="number" v-model="item.object.top" />
</div>
<div>
<span>透明度</span>
<input
type="range"
v-model="item.object.opacity"
step="0.1"
min="0"
max="1"
/>
</div>
</div>
<div class="box">
<pingpu :list="list" ref="pingpuRef" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, watch } from "vue";
import pingpu from "./pingpu.vue";
const data = ref([
{
url: "/src/assets/images/canvas/yinhua1.jpg",
offsetX: 0, // px
offsetY: 0, // px
angle: 0, // 角度
scale: 100, // %
gapX: 0, // px
gapY: 0, // px
const convertDotNotationToBracket = (str) =>
str.replace(/(?:^|\.)(\d+)(?=\.|$)/g, "[#$1]").replace(/\[#/g, "[");
object:{
scale: 1, // %
top: 0, // px
left: 0, // px
}
const list = ref([
{
id: "1",
ifSingle: false,
level2Type: "Pattern",
designType: "Library",
path: "/src/assets/images/canvas/yinhua1.jpg",
location: [0, 0],
scale: [1, 1],
angle: 0,
name: "Print2",
priority: 1,
object: {
top: 0,
left: 0,
scaleX: 1,
scaleY: 1,
opacity: 1,
angle: 0,
flipX: false,
flipY: false,
blendMode: "multiply",
gapX: 10,
gapY: 20,
},
},
{
url: "/src/assets/images/canvas/yinhua1.jpg",
offsetX: 0, // px
offsetY: 0, // px
angle: 0, // 角度
scale: 100, // %
gapX: 0, // px
gapY: 0, // px
object:{
scale: 1, // %
top: 0, // px
left: 0, // px
}
id: "2",
ifSingle: false,
level2Type: "Pattern",
designType: "Library",
path: "/src/assets/images/canvas/yinhua1.jpg",
location: [0, 0],
scale: [2, 2],
angle: -45,
name: "Print2",
priority: 1,
object: {
top: 150,
left: 250,
scaleX: 0.5,
scaleY: 0.5,
opacity: 1,
angle: 45,
flipX: false,
flipY: false,
blendMode: "multiply",
gapX: 0,
gapY: 0,
},
},
]);
// 深拷贝
const deepCopy = (obj) => JSON.parse(JSON.stringify(obj));
const oldList = ref(deepCopy(list.value));
const pingpuRef = ref(null);
watch(
() => list.value,
() => updateList(),
{ deep: true }
);
const updateList = () => {
const changeList = [];
oldList.value.forEach((oldItem) => {
const newItem = list.value.find((v) => v.id === oldItem.id);
if (newItem) {
const arr = findDiffProps(oldItem, newItem);
arr.forEach((item) => {
changeList.push({
...item,
id: oldItem.id,
action: "update-item",
key: convertDotNotationToBracket(item.key),
});
});
} else {
changeList.push({
id: oldItem.id,
action: "delete-item",
});
}
});
oldList.value = deepCopy(list.value);
console.log(changeList);
if (changeList.length) {
pingpuRef.value.updataList(changeList);
}
};
// 递归查找不同的属性
const findDiffProps = (obj1, obj2, diffProps = [], path = "") => {
for (const key in obj1) {
if (typeof obj1[key] === "object") {
findDiffProps(obj1[key], obj2[key], diffProps, `${path}${key}.`);
} else if (obj1[key] !== obj2[key]) {
diffProps.push({
key: `${path}${key}`,
oldValue: obj1[key],
newValue: obj2[key],
});
}
}
return diffProps;
};
</script>
<style lang='less' scoped>
.test {
display: flex;
> div {
width: 50%;
> .control {
margin-left: 10px;
margin-top: 10px;
> div {
display: flex;
align-items: center;
margin-bottom: 5px;
> span {
width: 80px;
}
> input {
padding-left: 10px;
border-radius: 5px;
}
> .control {
display: inline-block;
border: 1px solid #000;
padding: 10px;
margin: 10px;
border-radius: 10px;
> div {
display: flex;
align-items: center;
margin-bottom: 5px;
> span {
width: 80px;
}
> input {
padding-left: 10px;
border-radius: 5px;
}
}
> .box {
position: fixed;
top: 40%;
left: 10%;
width: 300px;
height: 500px;
overflow: hidden;
border: 1px solid #000;
}
}
> .box {
width: 400px;
height: 500px;
overflow: hidden;
border: 1px solid #000;
}
}
</style>