From 7f01ca25d89ddd719227a27c9da53a032053912e Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 17 Dec 2024 10:48:48 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B0=B0=EC=B9=98=EB=A9=B4=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=EC=84=A4=EC=A0=95,=20=EC=A7=80=EB=B6=95=EB=A9=B4?= =?UTF-8?q?=ED=95=A0=EB=8B=B9=20=EC=9E=91=EC=97=85=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/floor-plan/CanvasMenu.jsx | 1 - .../placementShape/PlacementShapeSetting.jsx | 15 +- .../modal/setting01/FirstOption.jsx | 4 +- src/hooks/common/useCanvasConfigInitialize.js | 4 +- src/hooks/common/useRoof.js | 7 +- src/hooks/common/useRoofFn.js | 143 ++++++++++++++++++ src/hooks/module/useModuleBasicSetting.js | 4 +- src/hooks/object/useObjectBatch.js | 4 +- src/hooks/option/useCanvasSetting.js | 19 ++- .../roofcover/useRoofAllocationSetting.js | 4 +- src/hooks/surface/usePlacementShapeDrawing.js | 4 +- src/hooks/surface/useSurfaceShapeBatch.js | 4 +- src/store/settingAtom.js | 3 +- src/util/canvas-util.js | 2 +- 14 files changed, 198 insertions(+), 20 deletions(-) create mode 100644 src/hooks/common/useRoofFn.js diff --git a/src/components/floor-plan/CanvasMenu.jsx b/src/components/floor-plan/CanvasMenu.jsx index 7ecb1d6a..235c0f61 100644 --- a/src/components/floor-plan/CanvasMenu.jsx +++ b/src/components/floor-plan/CanvasMenu.jsx @@ -82,7 +82,6 @@ export default function CanvasMenu(props) { const commonUtils = useRecoilValue(commonUtilsState) const { commonFunctions } = useCommonUtils() - const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector) const { floorPlanState, setFloorPlanState } = useContext(FloorPlanContext) const { restoreModuleInstArea } = useModuleBasicSetting() diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 5ea3b44f..66d7a8ec 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -65,6 +65,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set selected: true, layout: roofLayout, } + console.log('roofInfo', roofInfo) const addedRoofs = [] addedRoofs.push(roofInfo) @@ -208,7 +209,12 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
W
- +
@@ -218,7 +224,12 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
L
- +
diff --git a/src/components/floor-plan/modal/setting01/FirstOption.jsx b/src/components/floor-plan/modal/setting01/FirstOption.jsx index a9532efb..cfd9eea4 100644 --- a/src/components/floor-plan/modal/setting01/FirstOption.jsx +++ b/src/components/floor-plan/modal/setting01/FirstOption.jsx @@ -1,9 +1,8 @@ import React, { useEffect, useState } from 'react' -import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' import { useMessage } from '@/hooks/useMessage' import { POLYGON_TYPE } from '@/common/common' -import { setSurfaceShapePattern } from '@/util/canvas-util' import { useEvent } from '@/hooks/useEvent' +import { useRoofFn } from '@/hooks/common/useRoofFn' export default function FirstOption(props) { const { getMessage } = useMessage() @@ -11,6 +10,7 @@ export default function FirstOption(props) { let { canvas, settingModalFirstOptions, setSettingModalFirstOptions, settingsData, setSettingsData, settingsDataSave, setSettingsDataSave } = props const { option1, option2, dimensionDisplay } = settingModalFirstOptions const { initEvent } = useEvent() + const { setSurfaceShapePattern } = useRoofFn() // 데이터를 최초 한 번만 조회 useEffect(() => { diff --git a/src/hooks/common/useCanvasConfigInitialize.js b/src/hooks/common/useCanvasConfigInitialize.js index 18d1a052..2fafbb4b 100644 --- a/src/hooks/common/useCanvasConfigInitialize.js +++ b/src/hooks/common/useCanvasConfigInitialize.js @@ -2,12 +2,13 @@ import { useEffect } from 'react' import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { basicSettingState, roofDisplaySelector, settingModalFirstOptionsState } from '@/store/settingAtom' import { canvasState, dotLineGridSettingState, pitchText, pitchTextSelector, showAngleUnitSelector } from '@/store/canvasAtom' -import { getChonByDegree, getDegreeByChon, setSurfaceShapePattern } from '@/util/canvas-util' +import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util' import { useFont } from '@/hooks/common/useFont' import { useGrid } from '@/hooks/common/useGrid' import { globalFontAtom } from '@/store/fontAtom' import { useRoof } from '@/hooks/common/useRoof' import { usePolygon } from '@/hooks/usePolygon' +import { useRoofFn } from '@/hooks/common/useRoofFn' export function useCanvasConfigInitialize() { const canvas = useRecoilValue(canvasState) @@ -18,6 +19,7 @@ export function useCanvasConfigInitialize() { const setDotLineGridSetting = useSetRecoilState(dotLineGridSettingState) const pitchText = useRecoilValue(pitchTextSelector) const angleUnit = useRecoilValue(showAngleUnitSelector) + const { setSurfaceShapePattern } = useRoofFn() const {} = useFont() const {} = useGrid() const {} = useRoof() diff --git a/src/hooks/common/useRoof.js b/src/hooks/common/useRoof.js index 8aee0344..0b5e5a6b 100644 --- a/src/hooks/common/useRoof.js +++ b/src/hooks/common/useRoof.js @@ -2,12 +2,13 @@ import { canvasState } from '@/store/canvasAtom' import { allocDisplaySelector, roofDisplaySelector } from '@/store/settingAtom' import { useRecoilValue } from 'recoil' import { useEffect } from 'react' +import { useRoofFn } from '@/hooks/common/useRoofFn' export function useRoof() { const canvas = useRecoilValue(canvasState) const allocDisplay = useRecoilValue(allocDisplaySelector) const roofDisplay = useRecoilValue(roofDisplaySelector) - + const { setSurfaceShapePattern } = useRoofFn() useEffect(() => { if (!canvas) return canvas @@ -23,7 +24,7 @@ export function useRoof() { canvas.renderAll() }, [allocDisplay]) - const setSurfaceShapePattern = (polygon, mode = 'onlyBorder') => { + /*const setSurfaceShapePattern = (polygon, mode = 'onlyBorder') => { const ratio = window.devicePixelRatio || 1 let width = 265 / 10 @@ -145,7 +146,7 @@ export function useRoof() { polygon.set('fill', null) polygon.set('fill', pattern) polygon.canvas?.renderAll() - } + }*/ return {} } diff --git a/src/hooks/common/useRoofFn.js b/src/hooks/common/useRoofFn.js new file mode 100644 index 00000000..2e7e8160 --- /dev/null +++ b/src/hooks/common/useRoofFn.js @@ -0,0 +1,143 @@ +import { useRecoilValue } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { selectedRoofMaterialSelector } from '@/store/settingAtom' + +export function useRoofFn() { + const canvas = useRecoilValue(canvasState) + const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector) + + //면형상 선택 클릭시 지붕 패턴 입히기 + function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false) { + debugger + const ratio = window.devicePixelRatio || 1 + + let width = selectedRoofMaterial.width / 10 + let height = selectedRoofMaterial.length / 10 + let roofStyle = 2 + const inputPatternSize = { width: width, height: height } //임시 사이즈 + const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해 + + if (polygon.direction === 'east' || polygon.direction === 'west') { + //세로형이면 width height를 바꿈 + ;[patternSize.width, patternSize.height] = [inputPatternSize.height, patternSize.width] + } + + // 패턴 소스를 위한 임시 캔버스 생성 + const patternSourceCanvas = document.createElement('canvas') + patternSourceCanvas.width = polygon.width * ratio + patternSourceCanvas.height = polygon.height * ratio + const ctx = patternSourceCanvas.getContext('2d') + let offset = roofStyle === 1 ? 0 : patternSize.width / 2 + + const rows = Math.floor(patternSourceCanvas.height / patternSize.height) + const cols = Math.floor(patternSourceCanvas.width / patternSize.width) + + ctx.strokeStyle = mode === 'allPainted' ? 'black' : 'green' + ctx.lineWidth = mode === 'allPainted' ? 1 : 0.4 + ctx.fillStyle = mode === 'allPainted' ? 'rgba(0, 159, 64, 0.7)' : 'white' + + if (trestleMode) { + ctx.strokeStyle = 'black' + ctx.lineWidth = 0.2 + ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' + } else { + ctx.fillStyle = 'rgba(255, 255, 255, 1)' + } + + if (polygon.direction === 'east' || polygon.direction === 'west') { + offset = roofStyle === 1 ? 0 : patternSize.height / 2 + for (let col = 0; col <= cols; col++) { + const x = col * patternSize.width + const yStart = 0 + const yEnd = patternSourceCanvas.height + ctx.beginPath() + ctx.moveTo(x, yStart) // 선 시작점 + ctx.lineTo(x, yEnd) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) + } + + for (let row = 0; row <= rows; row++) { + const y = row * patternSize.height + (col % 2 === 0 ? 0 : offset) + const xStart = col * patternSize.width + const xEnd = xStart + patternSize.width + ctx.beginPath() + ctx.moveTo(xStart, y) // 선 시작점 + ctx.lineTo(xEnd, y) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height) + } + } + } + } else { + for (let row = 0; row <= rows; row++) { + const y = row * patternSize.height + + ctx.beginPath() + ctx.moveTo(0, y) // 선 시작점 + ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height) + } + + for (let col = 0; col <= cols; col++) { + const x = col * patternSize.width + (row % 2 === 0 ? 0 : offset) + const yStart = row * patternSize.height + const yEnd = yStart + patternSize.height + + ctx.beginPath() + ctx.moveTo(x, yStart) // 선 시작점 + ctx.lineTo(x, yEnd) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) + } + } + } + } + + const hachingPatternSourceCanvas = document.createElement('canvas') + + if (mode === 'lineHatch') { + hachingPatternSourceCanvas.width = polygon.width * ratio + hachingPatternSourceCanvas.height = polygon.height * ratio + + const ctx1 = hachingPatternSourceCanvas.getContext('2d') + + const gap = 10 + + ctx1.strokeStyle = 'green' // 선 색상 + ctx1.lineWidth = 0.3 // 선 두께 + + for (let x = 0; x < hachingPatternSourceCanvas.width + hachingPatternSourceCanvas.height; x += gap) { + ctx1.beginPath() + ctx1.moveTo(x, 0) // 선 시작점 + ctx1.lineTo(0, x) // 선 끝점 + ctx1.stroke() + } + } + + const combinedPatternCanvas = document.createElement('canvas') + combinedPatternCanvas.width = polygon.width * ratio + combinedPatternCanvas.height = polygon.height * ratio + const combinedCtx = combinedPatternCanvas.getContext('2d') + + // 첫 번째 패턴을 그린 후 두 번째 패턴을 덧입힘 + combinedCtx.drawImage(patternSourceCanvas, 0, 0) + combinedCtx.drawImage(hachingPatternSourceCanvas, 0, 0) + + // 패턴 생성 + const pattern = new fabric.Pattern({ + source: combinedPatternCanvas, + repeat: 'repeat', + }) + + polygon.set('fill', null) + polygon.set('fill', pattern) + polygon.canvas?.renderAll() + } + return { setSurfaceShapePattern } +} diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index fea7dbf8..7f1560ce 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -1,6 +1,6 @@ import { useRecoilState, useRecoilValue } from 'recoil' import { canvasState } from '@/store/canvasAtom' -import { rectToPolygon, setSurfaceShapePattern } from '@/util/canvas-util' +import { rectToPolygon } from '@/util/canvas-util' import { roofDisplaySelector } from '@/store/settingAtom' import offsetPolygon, { calculateAngle } from '@/util/qpolygon-utils' import { QPolygon } from '@/components/fabric/QPolygon' @@ -13,6 +13,7 @@ import { useSwal } from '@/hooks/useSwal' import { canvasSettingState } from '@/store/canvasAtom' import { compasDegAtom } from '@/store/orientationAtom' import { QLine } from '@/components/fabric/QLine' +import { useRoofFn } from '@/hooks/common/useRoofFn' export function useModuleBasicSetting() { const canvas = useRecoilValue(canvasState) @@ -23,6 +24,7 @@ export function useModuleBasicSetting() { const { swalFire } = useSwal() const canvasSetting = useRecoilValue(canvasSettingState) const compasDeg = useRecoilValue(compasDegAtom) + const { setSurfaceShapePattern } = useRoofFn() // const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useContext(EventContext) let selectedModuleInstSurfaceArray = [] diff --git a/src/hooks/object/useObjectBatch.js b/src/hooks/object/useObjectBatch.js index dd67b017..6fd858a1 100644 --- a/src/hooks/object/useObjectBatch.js +++ b/src/hooks/object/useObjectBatch.js @@ -5,13 +5,14 @@ import { useRecoilValue } from 'recoil' import { canvasState } from '@/store/canvasAtom' import { BATCH_TYPE, INPUT_TYPE } from '@/common/common' import { useEvent } from '@/hooks/useEvent' -import { pointsToTurfPolygon, polygonToTurfPolygon, rectToPolygon, setSurfaceShapePattern, triangleToPolygon } from '@/util/canvas-util' +import { pointsToTurfPolygon, polygonToTurfPolygon, rectToPolygon, triangleToPolygon } from '@/util/canvas-util' import { useSwal } from '@/hooks/useSwal' import * as turf from '@turf/turf' import { usePolygon } from '@/hooks/usePolygon' import { QPolygon } from '@/components/fabric/QPolygon' import { v4 as uuidv4 } from 'uuid' import { fontSelector } from '@/store/fontAtom' +import { useRoofFn } from '@/hooks/common/useRoofFn' export function useObjectBatch({ isHidden, setIsHidden }) { const { getMessage } = useMessage() @@ -20,6 +21,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { // const { addCanvasMouseEventListener, initEvent, addDocumentEventListener } = useContext(EventContext) const { swalFire } = useSwal() const { drawDirectionArrow } = usePolygon() + const { setSurfaceShapePattern } = useRoofFn() const lengthTextFont = useRecoilValue(fontSelector('lengthText')) useEffect(() => { diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 2a217956..499d15ea 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -21,6 +21,7 @@ import { basicSettingState, settingsState, roofMaterialsAtom, + selectedRoofMaterialSelector, } from '@/store/settingAtom' import { POLYGON_TYPE } from '@/common/common' import { globalFontAtom } from '@/store/fontAtom' @@ -28,6 +29,7 @@ import { dimensionLineSettingsState } from '@/store/commonUtilsAtom' import { gridColorState } from '@/store/gridAtom' import { useColor } from 'react-color-palette' import { useMasterController } from '@/hooks/common/useMasterController' +import { isObjectNotEmpty } from '@/util/common-utils' const defaultDotLineGridSetting = { INTERVAL: { @@ -92,7 +94,7 @@ export function useCanvasSetting() { const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState) - const { getRoofMaterialList } = useMasterController() + const { getRoofMaterialList, getModuleTypeItemList } = useMasterController() const [roofMaterials, setRoofMaterials] = useRecoilState(roofMaterialsAtom) const SelectOptions = [ @@ -102,18 +104,29 @@ export function useCanvasSetting() { { id: 4, name: '1/10', value: 1 / 10 }, ] + const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector) + useEffect(() => { addRoofMaterials() }, []) + useEffect(() => { + if (!selectedRoofMaterial || !isObjectNotEmpty(selectedRoofMaterial)) { + return + } + + const { id } = selectedRoofMaterial + console.log(getModuleTypeItemList(id)) + }, [selectedRoofMaterial]) + //지붕재 초기세팅 const addRoofMaterials = async () => { const { data } = await getRoofMaterialList() - const roofLists = data.map((item) => ({ + const roofLists = data.map((item, idx) => ({ ...item, id: item.roofMatlCd, name: item.roofMatlNm, - selected: false, + selected: idx === 0, nameJp: item.roofMatlNmJp, length: item.lenBase && parseInt(item.lenBase), width: item.widBase && parseInt(item.widBase), diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index 8ca6e8a1..ff20c0cb 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -1,7 +1,6 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom' import { useEffect, useState } from 'react' -import { setSurfaceShapePattern } from '@/util/canvas-util' import { useSwal } from '@/hooks/useSwal' import { usePolygon } from '@/hooks/usePolygon' import { basicSettingState, roofDisplaySelector, roofMaterialsSelector, selectedRoofMaterialSelector } from '@/store/settingAtom' @@ -13,6 +12,7 @@ import { useMessage } from '@/hooks/useMessage' import useMenu from '@/hooks/common/useMenu' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { menuTypeState } from '@/store/menuAtom' +import { useRoofFn } from '@/hooks/common/useRoofFn' // 지붕면 할당 export function useRoofAllocationSetting(id) { @@ -33,6 +33,8 @@ export function useRoofAllocationSetting(id) { const [roofList, setRoofList] = useState([]) const [editingLines, setEditingLines] = useState([]) + const { setSurfaceShapePattern } = useRoofFn() + useEffect(() => { setRoofList(basicSetting.roofs) }, [basicSetting]) diff --git a/src/hooks/surface/usePlacementShapeDrawing.js b/src/hooks/surface/usePlacementShapeDrawing.js index 95388ca4..92f1e06b 100644 --- a/src/hooks/surface/usePlacementShapeDrawing.js +++ b/src/hooks/surface/usePlacementShapeDrawing.js @@ -13,7 +13,7 @@ import { useMouse } from '@/hooks/useMouse' import { useLine } from '@/hooks/useLine' import { useTempGrid } from '@/hooks/useTempGrid' import { useEffect, useRef } from 'react' -import { distanceBetweenPoints, setSurfaceShapePattern } from '@/util/canvas-util' +import { distanceBetweenPoints } from '@/util/canvas-util' import { fabric } from 'fabric' import { calculateAngle } from '@/util/qpolygon-utils' import { @@ -33,6 +33,7 @@ import { POLYGON_TYPE } from '@/common/common' import { usePopup } from '@/hooks/usePopup' import { roofDisplaySelector } from '@/store/settingAtom' +import { useRoofFn } from '@/hooks/common/useRoofFn' // 면형상 배치 export function usePlacementShapeDrawing(id) { @@ -46,6 +47,7 @@ export function usePlacementShapeDrawing(id) { const { addLine, removeLine } = useLine() const { addPolygonByLines, drawDirectionArrow } = usePolygon() const { tempGridMode } = useTempGrid() + const { setSurfaceShapePattern } = useRoofFn() const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState) const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState) diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index 844829d3..99eed453 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil' import { canvasState, globalPitchState } from '@/store/canvasAtom' import { MENU, POLYGON_TYPE } from '@/common/common' -import { getIntersectionPoint, setSurfaceShapePattern } from '@/util/canvas-util' +import { getIntersectionPoint } from '@/util/canvas-util' import { degreesToRadians } from '@turf/turf' import { QPolygon } from '@/components/fabric/QPolygon' import { useSwal } from '@/hooks/useSwal' @@ -15,6 +15,7 @@ import { usePolygon } from '@/hooks/usePolygon' import { fontSelector } from '@/store/fontAtom' import { slopeSelector } from '@/store/commonAtom' import { QLine } from '@/components/fabric/QLine' +import { useRoofFn } from '@/hooks/common/useRoofFn' export function useSurfaceShapeBatch() { const { getMessage } = useMessage() @@ -29,6 +30,7 @@ export function useSurfaceShapeBatch() { const { addCanvasMouseEventListener, initEvent } = useEvent() // const { addCanvasMouseEventListener, initEvent } = useContext(EventContext) const { closePopup } = usePopup() + const { setSurfaceShapePattern } = useRoofFn() const applySurfaceShape = (surfaceRefs, selectedType, id) => { let length1, length2, length3, length4, length5 diff --git a/src/store/settingAtom.js b/src/store/settingAtom.js index 443a14f3..8ad558f7 100644 --- a/src/store/settingAtom.js +++ b/src/store/settingAtom.js @@ -220,8 +220,7 @@ export const selectedRoofMaterialSelector = selector({ key: 'selectedRoofMaterialSelector', get: ({ get }) => { const basicSetting = get(basicSettingState) - const roofMaterials = get(roofMaterialsAtom) - return basicSetting.selectedRoofMaterial ?? roofMaterials[0] + return basicSetting.selectedRoofMaterial }, }) diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index b27f91db..82c72da0 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -792,7 +792,7 @@ export const rectToPolygon = (rect) => { } //면형상 선택 클릭시 지붕 패턴 입히기 -export function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false) { +function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false) { const ratio = window.devicePixelRatio || 1 let width = 265 / 10