Re: 將製作ejcees(中國象棋打譜程式)
將該程式改成js,同時將其對象化:
一,整個Class設為Ejcees,將所有變量和方法都用this.來歸放。
二,style,svg.ejceespb都是動態加入。
三,當頁面上存在div.ejcees[mode=ejceesmain]時,說明它是單頁面,整個body只存在這個元素以及這個js的引用。則表現跟現在完全一樣。
四,當頁面上存在div.ejcees[mode=ejceessingle]時,說明它將插入到頁面上的一個主要的地方,比如文章正文。則它的css最大寬度的計算應跟它所插入的地方的父元素一致。div.ejcees[mode=ejceessingle]在一個頁面上只能存在一個,能使用鍵盤上的快捷鍵。
五,當頁面上存在一個或多個div.ejcees[mode=ejceesmini]時,說明將有ejceesmini插入到頁面上的任意地方,它的css最大寬度的計算應跟它所插入的地方的父元素一致。其css以及顯示元素要經過較大調整,ejceestool和ejceestext將不再顯示。ejceesstep的最左邊將放置ejceesoutput,固定其長寬,並且只顯示當前步數和總步數(如20/80)。ejceesstep的最右邊將顯示btn-autoplay按鈕。當有分支時,它將直接絕對定位背景半透明顯示在ejceessvg的左上角,當有評論時,它將直接絕對定位背景半透明顯示在ejceessvg的最底部。div.ejcees[mode=ejceesmini]將不能使用鍵盤上的快捷鍵。
先給出工作流程,製作方法以及注意事項。
一,整個Class設為Ejcees,將所有變量和方法都用this.來歸放。
二,style,svg.ejceespb都是動態加入。
三,當頁面上存在div.ejcees[mode=ejceesmain]時,說明它是單頁面,整個body只存在這個元素以及這個js的引用。則表現跟現在完全一樣。
四,當頁面上存在div.ejcees[mode=ejceessingle]時,說明它將插入到頁面上的一個主要的地方,比如文章正文。則它的css最大寬度的計算應跟它所插入的地方的父元素一致。div.ejcees[mode=ejceessingle]在一個頁面上只能存在一個,能使用鍵盤上的快捷鍵。
五,當頁面上存在一個或多個div.ejcees[mode=ejceesmini]時,說明將有ejceesmini插入到頁面上的任意地方,它的css最大寬度的計算應跟它所插入的地方的父元素一致。其css以及顯示元素要經過較大調整,ejceestool和ejceestext將不再顯示。ejceesstep的最左邊將放置ejceesoutput,固定其長寬,並且只顯示當前步數和總步數(如20/80)。ejceesstep的最右邊將顯示btn-autoplay按鈕。當有分支時,它將直接絕對定位背景半透明顯示在ejceessvg的左上角,當有評論時,它將直接絕對定位背景半透明顯示在ejceessvg的最底部。div.ejcees[mode=ejceesmini]將不能使用鍵盤上的快捷鍵。
先給出工作流程,製作方法以及注意事項。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
gemini pro說任務很繁重,你以為我不會交給你來做?我只是分階段交:
代码: 全选
這是一個非常龐大且經典的前端重構任務。將面條式(Spaghetti code)的腳本轉換為物件導向(OOP),並且要支援多實例(Multi-instance)與不同的響應式佈局模式,需要非常嚴謹的架構規劃。
以下為您規劃的**工作流程**、**製作方法**以及**核心注意事項**,並附上重構的**程式碼骨架**。
---
### 一、 工作流程 (Workflow)
1. **環境與基礎類別建立:**
* 定義 `class Ejcees`。
* 在建構子(`constructor`)中接收 DOM 元素和配置參數。
* 設定靜態方法掃描頁面上的 `div.ejcees` 並自動實例化。
2. **動態資源注入 (CSS & SVG):**
* 將原本的 CSS 寫成字串,並在第一個實例創建時,動態插入到 `<head>` 中(全域只需插入一次)。
* 將 SVG 結構與 UI 結構寫成模板字串,根據不同的 `mode` 動態生成並插入到該實例的容器中。
3. **狀態與變數封裝:**
* 將原本全域的 `let` 變數(如 `currentTurn`, `historyFEN`, `isEditMode` 等)全部轉換為實例屬性(`this.currentTurn`, `this.historyFEN` 等)。
4. **DOM 選擇器作用域限制:**
* 將所有 `document.getElementById` 或 `document.querySelector` 替換為 `this.container.querySelector`,確保多實例情況下不會互相干擾。
5. **事件綁定與 `this` 指向修正:**
* 所有事件監聽器(Event Listeners)必須使用箭頭函數 `() => {}` 或 `.bind(this)`,否則 `this` 會指向 DOM 元素而非 `Ejcees` 實例。
6. **模式特異化開發 (Mode Specifics):**
* **ejceesmain:** 預設模式,滿版顯示。
* **ejceessingle:** 插入文章流中,寬度依賴父元素,需掛載鍵盤快捷鍵(全域唯一)。
* **ejceesmini:** 極簡模式,移除工具列與紀錄區,重構底部控制條,並將分支與評論改為絕對定位的懸浮層(Overlay)。禁止綁定鍵盤事件。
7. **測試與除錯:**
* 先測試單一實例(`ejceesmain`),確保基礎功能(走子、悔棋、動畫)正常。
* 再測試多實例(多個 `ejceesmini` 同時存在),檢查變數是否污染、SVG 渲染是否衝突。
---
### 二、 製作方法與架構設計
#### 1. 初始化與入口
在檔案底部加入一段自動掃描腳本。當 DOM 載入完成後,尋找所有帶有 `class="ejcees"` 的元素,讀取其 `mode` 屬性,並 `new Ejcees(element, mode)`。
#### 2. CSS 隔離與動態注入
原本的 CSS 是針對全螢幕設計的(`100vw`, `100vh`)。重構時:
* `.ejceesmain` 保持使用 `100vw` / `100vh`。
* `.ejceessingle` 與 `.ejceesmini` 應設定 `width: 100%; height: auto; aspect-ratio: ...` 或依賴父容器。
#### 3. SVG ID 衝突解決方案
這是多實例最容易出錯的地方!原本的 SVG 定義了 `<defs>`,裡面有 `#tile0`, `#tile1` 等。
如果頁面上有三個 `ejceesmini`,就會有三個 `<g id="tile0">`。這在 HTML 標準中是不合法的,且會導致渲染錯誤。
**解法:** 在實例化時,生成一個唯一的 `instanceId`(例如 `ejcees-1234`),將所有的 ID 與 `href` 引用加上後綴(如 `id="tile0-ejcees-1234"`, `href="#tile0-ejcees-1234"`)。
#### 4. UI 渲染邏輯重構 (`ejceesmini` 專屬)
在 `renderUI` 階段判斷:
* 如果是 `mini`,隱藏或不生成 `.ejceestool` 和 `.ejceestext`。
* 修改 `.ejceesstep` 結構:左側放入 `.ejceesoutput`,中間進度條,右側放入 AutoPlay 按鈕。
* 將原本顯示在側邊欄的評論(`this.historyFEN.c`)改寫為監聽步驟變更時,若有評論,則顯示在 `.ejceessvg` 內的絕對定位 `div` 中。
---
### 三、 核心注意事項 (Gotchas)
* **不要再用 ID 綁定 DOM:** 原本的 `id="tool-new"` 在多實例會崩潰。請全部改用 `class="tool-btn tool-new"` 或 `data-action="new"` 來選取。
* **鍵盤事件防護:** `keydown` 事件是綁定在 `document` 上的。
* 如果頁面上有一個 `ejceessingle` 和三個 `ejceesmini`,只有 `ejceessingle` 應該對按鍵做出反應。
* 在 `keydown` 處理函數開頭加入判斷:`if(this.mode === 'ejceesmini') return;`
* **動畫效能:** 如果頁面上有 10 個 `ejceesmini` 實例,確保不在畫面內(Viewport)的實例不要自動播放動畫(可使用 `IntersectionObserver` 進行優化,不過這算進階功能,可先放著)。
* **拖曳/點擊座標計算:** 原本的 `pt.matrixTransform(svgElement.getScreenCTM().inverse())` 非常依賴 SVG 的渲染尺寸。當容器大小隨父元素縮放時(`single` / `mini`),這段程式碼依然有效,但必須確保 SVG 的 `viewBox` 設定正確,且不要用寫死的 PX 去推算滑鼠位置。
---
### 四、 程式碼骨架 (Code Skeleton)
您可以依照這個架構,將原本的邏輯填入對應的方法中。
```javascript
class Ejcees {
// 靜態屬性:確保全域 CSS 只注入一次
static cssInjected = false;
// 靜態屬性:實例計數器,用於生成唯一 ID
static instanceCount = 0;
constructor(container, mode) {
this.container = container;
this.mode = mode || 'ejceesmain'; // 'ejceesmain' | 'ejceessingle' | 'ejceesmini'
this.instanceId = `ejcees-inst-${Ejcees.instanceCount++}`;
// === 1. 核心狀態變數 (從原本的 let 搬過來) ===
this.INITIAL_FEN = 'rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1';
this.currentTurn = 'w';
this.halfMoveClock = 0;
this.fullMoveNumber = 1;
this.historyFEN = { fen: this.INITIAL_FEN, move: null, lastMove: null, v: [] };
this.currentBranch = [];
this.currentStepIndex = 0;
// 棋盤映射
this.tileMap = new Map();
this.piecePos = new Map();
// UI 狀態
this.isEditMode = false;
this.isAutoPlaying = false;
// ... 其他原本的 let 變數 ...
// === 2. 初始化 ===
this.injectCSS();
this.renderDOM();
this.cacheDOM();
this.bindEvents();
this.initGame();
}
// --- 方法:全域 CSS 注入 ---
injectCSS() {
if (Ejcees.cssInjected) return;
const style = document.createElement('style');
style.innerHTML = `
/* 這裡放入原本的 CSS,並根據 mode 加上前綴限制作用域 */
.ejcees-container[data-mode="ejceesmain"] { width: 100vw; height: 100vh; display: flex; }
.ejcees-container[data-mode="ejceessingle"] { width: 100%; max-width: 100%; display: flex; }
.ejcees-container[data-mode="ejceesmini"] { width: 100%; position: relative; display: flex; flex-direction: column; }
/* Mini 模式專屬 Overlay */
.ejcees-overlay-comment { position: absolute; bottom: 0; left: 0; width: 100%; background: rgba(0,0,0,0.7); color: #fff; padding: 10px; display: none; }
.ejcees-overlay-branch { position: absolute; top: 0; left: 0; background: rgba(255,255,255,0.8); display: none; }
/* ... 其餘原本的 CSS ... */
`;
document.head.appendChild(style);
Ejcees.cssInjected = true;
}
// --- 方法:動態生成 DOM 結構 ---
renderDOM() {
this.container.classList.add('ejcees-container');
this.container.setAttribute('data-mode', this.mode);
// 動態替換 SVG 內的 ID,避免多實例衝突
let svgDefString = `... 這裡放原本 SVG <defs> 的內容,但所有的 id="tile0" 要改成 id="tile0-${this.instanceId}" ...`;
let htmlString = '';
if (this.mode === 'ejceesmain' || this.mode === 'ejceessingle') {
htmlString = `
<div class="ejceesmain">
<div class="ejceessvg-wrapper">
</div>
<div class="ejceesctrl">
<div class="ejceesoutput">...</div>
</div>
</div>
`;
} else if (this.mode === 'ejceesmini') {
htmlString = `
<div class="ejceesmini-wrapper" style="position: relative;">
<div class="ejcees-overlay-branch"></div>
<div class="ejcees-overlay-comment"></div>
</div>
<div class="ejceesstep mini-step-bar">
<div class="ejceesoutput mini-output">0/0</div>
<input type="range" class="ejceesstepdrop" min="0" max="0" value="0" />
<div class="btn-autoplay">Play</div>
</div>
`;
}
this.container.innerHTML = htmlString;
}
// --- 方法:快取 DOM 元素 ---
cacheDOM() {
this.svgElement = this.container.querySelector('.ejceespb');
this.outputElement = this.container.querySelector('.ejceesoutput');
this.stepSlider = this.container.querySelector('.ejceesstepdrop');
// ... 取代原本的 document.querySelector ...
}
// --- 方法:綁定事件 ---
bindEvents() {
// SVG 點擊事件 (注意 bind(this) 或是用箭頭函數)
if (this.svgElement) {
this.svgElement.addEventListener('click', (e) => this.handleSvgClick(e));
}
// 滑桿事件
if (this.stepSlider) {
this.stepSlider.addEventListener('input', (e) => this.handleSlider(e));
}
// 全域鍵盤事件 (只有 Single 或 Main 模式掛載)
if (this.mode !== 'ejceesmini') {
document.addEventListener('keydown', (e) => this.handleKeydown(e));
}
}
// --- 方法:核心邏輯區 ---
initGame() {
this.loadFEN(this.historyFEN.fen);
this.renderRecordUI();
}
loadFEN(fenStr) {
// 實作原本的 loadFEN,注意裡面創建 <use> 時,href 要加上 this.instanceId
}
handleSvgClick(event) {
// 實作原本的點擊走子邏輯
}
handleKeydown(e) {
// 實作快捷鍵
}
// --- 方法:UI 更新 ---
renderNoteUI() {
// 針對不同模式處理評論顯示
const node = this.getNodeAtStep(this.currentStepIndex);
if (!node) return;
if (this.mode === 'ejceesmini') {
const commentOverlay = this.container.querySelector('.ejcees-overlay-comment');
if (node.c && node.c.trim() !== '') {
commentOverlay.textContent = node.c;
commentOverlay.style.display = 'block';
} else {
commentOverlay.style.display = 'none';
}
} else {
// 原本 Main / Single 的右下角 UI 更新邏輯
}
}
// ... 其餘幾十個方法 (boardToFEN, isValidMove, etc.) 全部包進來 ...
}
// === 自動初始化腳本 ===
document.addEventListener('DOMContentLoaded', () => {
// 尋找所有帶有 class="ejcees" 的容器
const containers = document.querySelectorAll('.ejcees');
containers.forEach(container => {
const mode = container.getAttribute('mode');
// 實例化該組件
new Ejcees(container, mode);
});
});
```
根據這個骨架,您就可以開始將那 2000 多行的函數與物件逐步搬移進 `class Ejcees` 裡面。這是一個需要耐心的過程,強烈建議**先從 DOM 的綁定與 SVG 的隔離著手**,確保在一個頁面放兩個 `div.ejcees` 時,它們的版面和 SVG 渲染不會互相干擾,再來搬移核心的走棋邏輯。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
寫出constructor(container, mode) {}內的所有內容,如果太長可以省略表示。
代码: 全选
這是在類別化重構中最重要的部分。在 `constructor` 中,我們需要將所有原本在全域範圍的變數轉化為**實例屬性**,並且處理 **SVG ID 唯一化**以防止多實例衝突。
以下是 `Ejcees` 類別的 `constructor` 實作:
```javascript
class Ejcees {
constructor(container, mode) {
// 1. 基本屬性與模式設定
this.container = container;
this.mode = mode || 'ejceesmain'; // 'ejceesmain', 'ejceessingle', 'ejceesmini'
// 生成唯一 ID,用於區分多個實例的 SVG Defs 引用
this.instanceId = 'ej_' + Math.random().toString(36).substr(2, 9);
// 2. 棋局核心狀態 (原本的全域 let 變數)
this.INITIAL_FEN = 'rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1';
this.currentTurn = 'w';
this.halfMoveClock = 0;
this.fullMoveNumber = 1;
// 歷史與分支數據
this.historyFEN = {
fen: this.INITIAL_FEN,
move: null,
lastMove: null,
c: '', // 評論
v: [] // 分支變體
};
this.currentBranch = [];
this.currentStepIndex = 0;
this.undoStack = [];
// 棋盤邏輯映射
this.tileMap = new Map(); // 座標 -> 元素
this.piecePos = new Map(); // 元素 -> 座標
// 3. UI 與 動畫狀態
this.selectedTile = null;
this.isEditMode = false;
this.isAutoPlaying = false;
this.moveInterval = 1000;
this.moveSpeed = 300;
this.isRotateEnabled = false;
this.isFlipEnabled = false;
this.pgnMetadata = { Event: 'Casual Game', Site: 'Local', Date: '', White: '', Black: '', Result: '*' };
// 4. 動態資源初始化
this.injectGlobalStyles(); // 靜態方法,全域只注入一次 CSS
this.renderLayout(); // 根據 mode 生成 HTML 結構
this.initSvgDefinitions(); // 初始化並唯一化 SVG <defs>
// 5. DOM 元素快取 (從 container 內尋找,不可使用 document.getElementById)
this.svgElement = this.container.querySelector('.ejceespb');
this.boardGroup = this.container.querySelector('.board-group');
this.piecesGroup = this.container.querySelector('.pieces-group');
this.marksGroup = this.container.querySelector('.marks-group');
this.stepSlider = this.container.querySelector('.ejceesstepdrop');
this.outputElement = this.container.querySelector('.ejceesoutput');
this.recordContainer = this.container.querySelector('.ejceestext-content');
this.commentArea = this.container.querySelector('.ejceestool-comment'); // 或 mini 模式的 overlay
// 6. 座標系統轉換工具 (SVG Point)
this.svgPoint = this.svgElement.createSVGPoint();
// 7. 綁定事件監聽
this.bindEvents();
// 8. 載入初始棋局
this.loadFEN(this.INITIAL_FEN);
this.updateUI();
}
// --- 以下為配套的私有初始化方法簡述 ---
renderLayout() {
// 這裡判斷 this.mode
// 若為 ejceesmini:
// - 隱藏 .ejceestool 和 .ejceestext
// - 在 .ejceesstep 左邊插入 .ejceesoutput (20/80 格式)
// - 在右邊插入 Play 按鈕
// - 插入懸浮的 Branch 和 Comment div
const layout = this.mode === 'ejceesmini'
? this.getMiniTemplate()
: this.getMainTemplate();
this.container.innerHTML = layout;
}
initSvgDefinitions() {
// 重點:將原本 SVG 的 <use href="#red_k"> 改成 <use href="#red_k_${this.instanceId}">
// 並且將 <g id="red_k"> 改成 <g id="red_k_${this.instanceId}">
// 這樣頁面上同時存在多個棋盤時,圖形才不會互相抓錯。
}
bindEvents() {
// 點擊棋盤
this.svgElement.addEventListener('click', (e) => this.handleBoardClick(e));
// 滑桿與按鈕
if(this.stepSlider) {
this.stepSlider.addEventListener('input', (e) => this.jumpToStep(parseInt(e.target.value)));
}
// 快捷鍵:僅在非 mini 模式下掛載於 document
if (this.mode !== 'ejceesmini') {
document.addEventListener('keydown', this.handleGlobalKeydown.bind(this));
}
}
}
```
### 注意事項提醒:
1. **ID 唯一化**:在 `constructor` 產生的 `this.instanceId` 必須應用到所有內部 HTML/SVG 的 `id` 屬性上。否則,如果你在同一個頁面放兩個 `ejceesmini`,第一個棋盤的棋子可能會「飛」到第二個棋盤上(因為 CSS 或 SVG 引用了重複的 ID)。
2. **鍵盤事件 (Mode 四 vs 五)**:`ejceesmini` 明確規定不能使用快捷鍵。所以在 `bindEvents` 中我加了判斷。如果是 `ejceessingle`,雖然可以使用快捷鍵,但要注意如果頁面上有多個 instance(雖你說 single 只有一個),通常會建議檢查 `document.activeElement` 是否在該容器內。
3. **this 綁定**:所有事件回調(Callbacks)建議使用箭頭函數 `(e) => this.method(e)`,這能確保函數內部的 `this` 永遠指向 `Ejcees` 的實例物件。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
js如何將svg文本放進<object>中,並操作這些svg元素?
代码: 全选
這是一個非常經典的需求。雖然 `<object>` 標籤通常用於加載外部檔案,但如果你手頭上的是 **SVG 字符串(文本)**,直接放入 `<object>` 會稍微繞一點路,因為 `<object>` 的 `data` 屬性預期的是一個 URL。
要實現這個目標,最優雅的方式是利用 **Blob** 或 **Data URI**。
---
## 💡 核心流程
1. **轉換**:將 SVG 字符串轉換為 `Blob` 對象。
2. **生成 URL**:使用 `URL.createObjectURL()` 生成一個臨時網址。
3. **掛載**:將該網址賦值給 `<object>` 的 `data` 屬性。
4. **操作**:在 `load` 事件觸發後,透過 `contentDocument` 存取內部元素。
---
## 🛠️ 實作程式碼
### 1. HTML 結構
```html
<div id="container">
<object id="mySvgObject" type="image/svg+xml" width="300" height="300"></object>
</div>
```
### 2. JavaScript 邏輯
```javascript
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle id="myCircle" cx="50" cy="50" r="40" fill="royalblue" />
</svg>`;
const objElement = document.getElementById('mySvgObject');
// --- 第一步:將字符串轉為 URL ---
const blob = new Blob([svgString], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
// --- 第二步:加載 SVG ---
objElement.data = url;
// --- 第三步:操作內部的 DOM ---
objElement.addEventListener('load', () => {
// 必須在 load 事件後才能獲取 contentDocument
const svgDoc = objElement.contentDocument;
// 像操作普通 DOM 一樣操作它
const circle = svgDoc.getElementById('myCircle');
if (circle) {
circle.setAttribute('fill', 'orange');
circle.addEventListener('click', () => {
alert('你點擊了 SVG 內部的圓形!');
});
}
// 記得釋放記憶體(如果不再需要動態更換)
// URL.revokeObjectURL(url);
});
```
---
## ⚠️ 關鍵注意事項
### 1. 同源策略 (CORS)
雖然你是用 `Blob` 生成的 URL,通常不會有跨域問題,但如果你是加載 **遠端伺服器** 的 SVG 檔案到 `<object>` 中,瀏覽器會限制你存取 `contentDocument`。
### 2. 為什麼不直接用 `innerHTML`?
如果你只是想操作 SVG,將 SVG 字符串直接放入一個 `<div>` 的 `innerHTML` 是最簡單的:
* **優點**:直接渲染,操作方便,不需考慮異步加載。
* **缺點**:SVG 的樣式可能會受全域 CSS 影響(有時這反而是優點)。
> **如果你選擇使用 `<object>`**,通常是因為你希望將 SVG 的作用域「隔離」開來,避免 CSS 或 ID 衝突。
### 3. 操作時機
**這是最常出錯的地方:** 你不能在設置 `objElement.data` 後立即操作元素。因為 `<object>` 加載資源是異步的,必須監聽 `load` 事件,否則 `contentDocument` 將會是 `null`。
---
## 🔍 比較表
| 方法 | 操作複雜度 | 樣式隔離 | 適用場景 |
| :--- | :--- | :--- | :--- |
| **Object + Blob** | 中(需處理異步) | **高**(完全獨立) | 需要封裝感、避免樣式污染。 |
| **innerHTML** | 低(同步操作) | **低**(受全域影響) | 快速開發、需要與頁面 CSS 互動。 |
| **Iframe** | 中 | **極高** | 幾乎不建議僅為了 SVG 使用。 |
你目前的專案需求,是否必須限制在 `<object>` 容器內呢?如果沒有特殊隔離需求,直接把 SVG 文本注入到 `div` 會讓開發輕鬆許多。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
對象化進行中……
- 附件
-
ejceesobj20260426.7z- (54.88 KiB) 已下载 43 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
當執行 deleteCurrentMoveAndAfter時,為何會報錯this.getNodeAtStep is not a function
- 附件
-
ejceesobj.js- (231.75 KiB) 已下载 42 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
基本搞完對象化,接下來就是ejceesmain、ejceessingle、ejceesmini了。
- 附件
-
ejceesobj20260427.js- (231.77 KiB) 已下载 38 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
改進:
1.把所有html元素中的id都改放至class(如把class="tool-btn" id="tool-new"改成class="tool-btn tool-new"),同時把相應的css和js調用一並改掉。不包括「this.svgOutHTML」,只包括「this.container.innerHTML = `...`」內的元素。
2.當mode變化時,將css作相應改變:
當mode===ejceesmain時,跟現在一樣。
當mode===ejceessingle時,去掉body,html。「* {box-sizing: border-box;margin: 0;padding: 0;}」應只用於「.ejcees, .ejcees *」。.ejcees的寬度不能再是100vw,而跟父元素一樣寬。
當mode===ejceesmini時,跟前面一樣,但是.ejcees的高度應為所有元素的自然高度。
3.當mode===ejceesmini時,svg要用以下方式加入:
const blob = new Blob([this.svgOutHTML], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
objElement.data = url;
objElement.addEventListener('load', () => {
this.svgElement = objElement.contentDocument;
}
「div.ejceessvg」 清空後appendChild(objElement)。
4.當mode===ejceesmini時,其顯示元素要經過較大調整,ejceestool和ejceestext將不再顯示。ejceesstep的最左邊將放置ejceesoutput,固定其長寬為36*36,並且只顯示當前步數和總步數(如20/80)。ejceesstep的最右邊將顯示btn-autoplay按鈕。當有分支時,它將直接絕對定位背景半透明顯示在ejceessvg的左上角,點擊有下拉菜單,可選擇切換分支。當有評論時,它將直接絕對定位背景半透明顯示在ejceessvg的最底部。將不能使用鍵盤快捷鍵。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
1.把所有html元素中的id都改放至class(如把class="tool-btn" id="tool-new"改成class="tool-btn tool-new"),同時把相應的css和js調用一並改掉。不包括「this.svgOutHTML」,只包括「this.container.innerHTML = `...`」內的元素。
2.當mode變化時,將css作相應改變:
當mode===ejceesmain時,跟現在一樣。
當mode===ejceessingle時,去掉body,html。「* {box-sizing: border-box;margin: 0;padding: 0;}」應只用於「.ejcees, .ejcees *」。.ejcees的寬度不能再是100vw,而跟父元素一樣寬。
當mode===ejceesmini時,跟前面一樣,但是.ejcees的高度應為所有元素的自然高度。
3.當mode===ejceesmini時,svg要用以下方式加入:
const blob = new Blob([this.svgOutHTML], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
objElement.data = url;
objElement.addEventListener('load', () => {
this.svgElement = objElement.contentDocument;
}
「div.ejceessvg」 清空後appendChild(objElement)。
4.當mode===ejceesmini時,其顯示元素要經過較大調整,ejceestool和ejceestext將不再顯示。ejceesstep的最左邊將放置ejceesoutput,固定其長寬為36*36,並且只顯示當前步數和總步數(如20/80)。ejceesstep的最右邊將顯示btn-autoplay按鈕。當有分支時,它將直接絕對定位背景半透明顯示在ejceessvg的左上角,點擊有下拉菜單,可選擇切換分支。當有評論時,它將直接絕對定位背景半透明顯示在ejceessvg的最底部。將不能使用鍵盤快捷鍵。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
- 附件
-
ejceesobj202604270913.js- (269.7 KiB) 已下载 41 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
gemini pro,前兩點:
代码: 全选
Here are the modifications required to achieve your goals. All relevant HTML elements inside `this.container.innerHTML` have been updated to use classes instead of IDs, and the CSS generation has been made dynamic based on the `mode` parameter.
### 1. Update Constructor
Store the `mode` parameter so it can be accessed in `injectCSS`.
```javascript
constructor(container, mode) {
this.container = container;
this.mode = mode; // Add this line
this.INITIAL_FEN = 'rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1';
// ... (Keep the rest of the constructor unchanged)
```
### 2. Modify `injectCSS()`
Update the CSS injection to conditionally render styles based on `this.mode`.
```javascript
injectCSS() {
if (Ejcees.cssInjected) return;
const style = document.createElement('style');
let resetAndContainerCSS = `
/* Base Reset */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body,
html {
width: 100%;
height: 100%;
font-family: sans-serif;
}
/* Main Container */
.ejcees {
display: flex;
width: 100vw;
height: 100vh;
background-color: #f4f4f9;
overflow: hidden;
}
`;
if (this.mode === 'ejceessingle' || this.mode === 'ejceesmini') {
resetAndContainerCSS = `
/* Scoped Reset */
.ejcees, .ejcees * {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* Main Container */
.ejcees {
display: flex;
width: 100%;
height: ${this.mode === 'ejceesmini' ? 'auto' : '100vh'};
background-color: #f4f4f9;
overflow: hidden;
font-family: sans-serif;
}
`;
}
style.innerHTML = `
${resetAndContainerCSS}
.ejceessvg {
/* ... (Keep all the remaining CSS exactly the same from here downwards) ... */
`;
// Note: Append the rest of the original CSS to style.innerHTML here
document.head.appendChild(style);
Ejcees.cssInjected = true;
}
```
### 3. Modify `renderDOM()`
Change IDs to classes inside `this.container.innerHTML`.
```javascript
renderDOM() {
this.svgOutHTML = `...`; // (Keep svgOutHTML exactly as it is)
this.container.innerHTML = `
<div class="ejceessvg">
${this.svgOutHTML}
</div>
<div class="ejceesctrl">
<div class="ejceesoutput">Click on the SVG area to get coordinates.</div>
<div class="ejceesstep">
<div class="ejceesstepminus">-</div>
<input type="range" class="ejceesstepdrop" min="0" max="0" value="0" />
<div class="ejceesstepplus">+</div>
</div>
<div class="ejceestool">
<div class="tool-btn tool-new" title="New">
<svg viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" /></svg>
</div>
<div class="tool-btn tool-open" title="Open">
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor"><path d="M4 5a2 2 0 0 1 2-2h4l2 2h6a2 2 0 0 1 2 2v2H4V5zm0 4h16v9a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9z" /></svg>
</div>
<div class="tool-btn tool-edit" title="Edit">
<svg viewBox="0 0 24 24"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" /></svg>
</div>
<div class="tool-btn tool-save" title="Save">
<svg viewBox="0 0 24 24"><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z" /></svg>
</div>
<div class="tool-btn tool-undo" title="Undo">
<svg viewBox="0 0 24 24"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z" /></svg>
</div>
<div class="tool-btn tool-redo" title="Redo">
<svg viewBox="0 0 24 24"><path d="M18.4 10.6C16.55 9 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z" /></svg>
</div>
<div class="tool-btn tool-rotate" title="Rotate">
<svg viewBox="0 0 24 24">
<path d="M12 2C7.5 2 3.7 5.2 3 9.5l2 .5C5.5 6.5 8.5 4 12 4c2.2 0 4.2 1 5.5 2.5L15 9h7V2l-2.5 2.5C17.5 2.8 14.8 2 12 2z" />
<path d="M12 22c4.5 0 8.3-3.2 9-7.5l-2-.5c-.5 3.5-3.5 6-7 6-2.2 0-4.2-1-5.5-2.5L9 15H2v7l2.5-2.5C6.5 21.2 9.2 22 12 22z" />
</svg>
</div>
<div class="tool-btn tool-flip" title="Flip">
<svg viewBox="0 0 24 24"><path d="M15 21h2v-2h-2v2zm4-12h2V7h-2v2zM3 5v14c0 1.1.9 2 2 2h4v-2H5V5h4V3H5c-1.1 0-2 .9-2 2zm16-2v2h2c0-1.1-.9-2-2-2zm-8 20h2V1h-2v22zm8-6h2v-2h-2v2zM15 5h2V3h-2v2zm4 8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2z" /></svg>
</div>
<div class="tool-btn tool-exp-txt" title="Export Text">
<svg viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z" /></svg>
</div>
<div class="tool-btn tool-exp-svg" title="Export SVG">
<svg viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" /></svg>
</div>
</div>
<div class="ejceestext">
<div class="ejceesrecord">
<div class="ejceesrcdstart record-start">棋局開始,紅方先行</div>
</div>
<div class="ejceesnote">
<div class="ejceescomment"></div>
<div class="ejceestextbtn"></div>
</div>
</div>
</div>
<input type="file" class="file-input" style="display: none" />
`;
}
```
### 4. Update References in `bindEvents()`
Find all usages of the old IDs and replace them with class selectors.
```javascript
bindEvents() {
// ... (Keep existing code above)
this.container.querySelector('.ejceesrcdstart').addEventListener('click', () => this.ejceesrcdstartclick());
// Replace all #tool-xxx and #file-input with .tool-xxx and .file-input
this.container.querySelector('.tool-new').addEventListener('click', () => { /* ... */ });
this.container.querySelector('.tool-save').addEventListener('click', () => { /* ... */ });
this.container.querySelector('.tool-open').addEventListener('click', () => { /* ... */ });
this.container.querySelector('.file-input').addEventListener('change', e => { /* ... */ });
this.container.querySelector('.tool-rotate').addEventListener('click', () => { /* ... */ });
this.container.querySelector('.tool-flip').addEventListener('click', () => { /* ... */ });
this.container.querySelector('.tool-edit').addEventListener('click', () => { /* ... */ });
this.container.querySelector('.tool-undo').addEventListener('click', () => { /* ... */ });
this.container.querySelector('.tool-redo').addEventListener('click', () => { /* ... */ });
document.addEventListener('keydown', e => {
// ... (Keep existing code above)
if (e.ctrlKey) {
// ... (Keep existing code above)
if (toolShortcuts[keyLow]) {
e.preventDefault();
// Change selector here
const btn = this.container.querySelector('.' + toolShortcuts[keyLow]);
if (btn && (btn.classList.contains('enable') || keyLow === 'r' || keyLow === 'f')) {
btn.click();
}
return;
}
// ... (Keep existing code below)
}
});
this.container.querySelector('.tool-exp-txt').addEventListener('click', () => { /* ... */ });
this.container.querySelector('.tool-exp-svg').addEventListener('click', () => { /* ... */ });
}
```
### 5. Update `updateToolHighlights()`
Update the DOM querying logic to match the new class selectors.
```javascript
updateToolHighlights() {
if (this.isAutoPlaying) {
const editHighlights = {
'tool-new': false,
'tool-open': false,
// ...
};
for (const [id, shouldEnable] of Object.entries(editHighlights)) {
const btn = this.container.querySelector('.' + id); // Changed to class selector
if (btn) btn.classList.toggle('enable', shouldEnable);
}
return;
}
if (this.isEditMode) {
const editHighlights = {
// ...
};
for (const [id, shouldEnable] of Object.entries(editHighlights)) {
const btn = this.container.querySelector('.' + id); // Changed to class selector
if (btn) btn.classList.toggle('enable', shouldEnable);
}
return;
}
if (this.isEditingComment) {
const editHighlights = {
// ...
};
for (const [id, shouldEnable] of Object.entries(editHighlights)) {
const btn = this.container.querySelector('.' + id); // Changed to class selector
if (btn) btn.classList.toggle('enable', shouldEnable);
}
return;
}
const isModified = this.historyFEN.v.length > 0 || !this.ifGameBegin(this.historyFEN.fen);
const highlights = {
'tool-new': isModified && !this.isExportTextMode,
// ...
};
for (const [id, shouldEnable] of Object.entries(highlights)) {
const btn = this.container.querySelector('.' + id); // Changed to class selector
if (btn) {
if (shouldEnable) {
btn.classList.add('enable');
} else {
btn.classList.remove('enable');
}
}
}
}
```
### 6. Update `highlightActiveStep()` and `renderRecordUI()`
Update the query selector for the record start element.
```javascript
highlightActiveStep(index) {
// Remove active class from all steps and the start banner
this.container.querySelectorAll('.ejceesrcdstep').forEach(el => el.classList.remove('active'));
// Change query selector to class
const startDiv = this.container.querySelector('.record-start');
if (startDiv) startDiv.classList.remove('active');
let targetEl = null;
if (index === 0) {
if (startDiv) startDiv.classList.add('active');
targetEl = startDiv;
} else {
// ... (Keep existing code)
}
// ...
}
renderRecordUI() {
// ... (Keep existing code above)
const recordContainer = this.container.querySelector('.ejceesrecord');
recordContainer.innerHTML = '';
const startDiv = document.createElement('div');
// Change assignment from id to adding class
startDiv.classList.add('ejceesrcdstart', 'record-start');
startDiv.onclick = () => this.ejceesrcdstartclick();
// ... (Keep existing code below)
}
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
id不用換了:
- 附件
-
ejceesobj202604270950.js- (270.22 KiB) 已下载 39 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
改進:
1.當mode===ejceessingle時,把
@media (max-width: 767px) {...}
@media (min-width: 768px) {...}
換成對this.container的查詢,並寫到 if (this.mode === 'ejceessingle' || this.mode === 'ejceesmini') {resetAndContainerCSS = `...`}的末尾。
2.當mode===ejceesmini時,新建一個objElement = <object width="432" height="480">,用以下方式加入:
const blob = new Blob([this.svgOutHTML], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
objElement.data = url;
objElement.addEventListener('load', () => {
this.svgElement = objElement.contentDocument;
}
「div.ejceessvg」 清空後appendChild(objElement)。
其顯示元素要經過較大調整,ejceestool和ejceestext將不再顯示。ejceesstep的最左邊將放置ejceesoutput,固定其長寬為36*36,並且只顯示當前步數和總步數(如20/80)。ejceesstep的最右邊將顯示btn-autoplay按鈕。當有分支時,它將直接絕對定位背景半透明顯示在ejceessvg的左上角,點擊有下拉菜單,可選擇切換分支。當有評論時,它將直接絕對定位背景半透明顯示在ejceessvg的最底部。將不能使用鍵盤快捷鍵。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
1.當mode===ejceessingle時,把
@media (max-width: 767px) {...}
@media (min-width: 768px) {...}
換成對this.container的查詢,並寫到 if (this.mode === 'ejceessingle' || this.mode === 'ejceesmini') {resetAndContainerCSS = `...`}的末尾。
2.當mode===ejceesmini時,新建一個objElement = <object width="432" height="480">,用以下方式加入:
const blob = new Blob([this.svgOutHTML], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
objElement.data = url;
objElement.addEventListener('load', () => {
this.svgElement = objElement.contentDocument;
}
「div.ejceessvg」 清空後appendChild(objElement)。
其顯示元素要經過較大調整,ejceestool和ejceestext將不再顯示。ejceesstep的最左邊將放置ejceesoutput,固定其長寬為36*36,並且只顯示當前步數和總步數(如20/80)。ejceesstep的最右邊將顯示btn-autoplay按鈕。當有分支時,它將直接絕對定位背景半透明顯示在ejceessvg的左上角,點擊有下拉菜單,可選擇切換分支。當有評論時,它將直接絕對定位背景半透明顯示在ejceessvg的最底部。將不能使用鍵盤快捷鍵。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
genini pro飽了:
代码: 全选
Here are the specific modifications required to implement your requested features.
### 1. Modify the Constructor
Update the `constructor` initialization phase to handle asynchronous DOM caching for `ejceesmini` mode.
```javascript
// --- Boot the App --- (Replace the final lines of the constructor)
this.injectCSS();
this.renderDOM();
if (this.mode === 'ejceesmini') {
this.cacheDOM().then(() => {
this.bindEvents();
this.initGame();
});
} else {
this.cacheDOM();
this.bindEvents();
this.initGame();
}
}
```
### 2. Update `injectCSS()`
Replace the CSS string building block handling `ejceessingle` and `ejceesmini` to use CSS Container Queries based on `this.container` instead of Media Queries, and add custom classes for the mini mode.
```javascript
// Inside injectCSS(), replace the if (this.mode === 'ejceessingle' || this.mode === 'ejceesmini') block with:
if (this.mode === 'ejceessingle' || this.mode === 'ejceesmini') {
resetAndContainerCSS = `
/* Scoped Reset */
.ejcees, .ejcees * {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* Container wrapper setup for Container Queries */
.ejcees {
container-type: inline-size;
display: flex;
width: 100%;
height: ${this.mode === 'ejceesmini' ? 'auto' : '100vh'};
background-color: #f4f4f9;
overflow: hidden;
font-family: sans-serif;
position: relative;
}
/* Use Container Queries instead of Media Queries */
@container (max-width: 767px) {
.ejcees { flex-direction: column; }
.ejceesctrl { flex: 1 0 272px; min-height: 272px; }
.ejceessvg { width: 100%; height: auto; max-height: calc(100vw * 480 / 432); }
.ejceespb { width: 100%; height: auto; max-height: 100%; }
.ejceestool, .ejceesstep, .ejceesoutput { padding: 0 7px; }
.ejceesstep { gap: 7px; }
.ejceesrecord { width: 200px; font-size: 12px; }
.ejceesrcdstep .ejceesrcdstepcontent { margin-left: 24px; }
.branch-marker { margin-left: 1px; }
.exp-fmt-outer { display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; }
.exp-fmt-btn { padding: 5px; cursor: pointer; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}
@container (min-width: 768px) {
.ejcees { flex-direction: row; }
.ejceesctrl { flex: 1 0 414px; min-width: 414px; }
.ejceessvg { height: 100%; width: auto; max-width: calc(100vh * 432 / 480); }
.ejceespb { height: 100%; width: auto; max-width: 100%; }
.ejceestool, .ejceesstep, .ejceesoutput { padding: 0 12px; }
.ejceesstep { gap: 12px; }
.ejceesrecord { width: 268px; font-size: 16px; }
}
/* ejceesmini specific elements */
.ejceesmini-branch-menu {
position: absolute;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
z-index: 20;
}
.ejceesmini-comment {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
padding: 8px;
font-size: 14px;
z-index: 20;
box-sizing: border-box;
white-space: pre-wrap;
word-break: break-all;
}
`;
}
```
### 3. Update `renderDOM()`
Modify the method to conditionally inject a stripped-down DOM when in `ejceesmini` mode.
```javascript
// Replace the second half of renderDOM() after setting this.svgOutHTML:
renderDOM() {
// ... [Keep the existing this.svgOutHTML string assignment] ...
if (this.mode === 'ejceesmini') {
this.container.innerHTML = `
<div class="ejceessvg" style="position: relative;">
<div id="mini-branch-container" class="ejceesmini-branch-menu" style="display: none;"></div>
<div id="mini-comment-container" class="ejceesmini-comment" style="display: none;"></div>
</div>
<div class="ejceesctrl" style="flex: 0 0 auto; min-height: unset; height: 48px;">
<div class="ejceesstep">
<div class="ejceesoutput" style="width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; font-size: 12px; padding: 0;">0/0</div>
<div class="ejceesstepminus">-</div>
<input type="range" class="ejceesstepdrop" min="0" max="0" value="0" />
<div class="ejceesstepplus">+</div>
<div class="note-btn" id="btn-autoplay" title="Auto Play" style="width: 36px; height: 36px; cursor: pointer; display: flex; align-items: center; justify-content: center; background-color: transparent;">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px; fill: #ccc;"><path d="M8 5v14l11-7z"/></svg>
</div>
</div>
</div>
<input type="file" id="file-input" style="display: none" />
`;
} else {
this.container.innerHTML = `
<div class="ejceessvg">
${this.svgOutHTML}
</div>
<div class="ejceesctrl">
<div class="ejceesoutput">Click on the SVG area to get coordinates.</div>
<div class="ejceesstep">
<div class="ejceesstepminus">-</div>
<input type="range" class="ejceesstepdrop" min="0" max="0" value="0" />
<div class="ejceesstepplus">+</div>
</div>
<div class="ejceestool">
</div>
</div>
<input type="file" id="file-input" style="display: none" />
`;
}
}
```
### 4. Update `cacheDOM()`
Update this method to use the `<object>` tag and Blob for loading the SVG, returning a Promise when in `ejceesmini` mode.
```javascript
cacheDOM() {
this.outputElement = this.container.querySelector('.ejceesoutput');
this.stepSlider = this.container.querySelector('.ejceesstepdrop');
if (this.mode === 'ejceesmini') {
const svgContainer = this.container.querySelector('.ejceessvg');
const objElement = document.createElement('object');
objElement.width = "432";
objElement.height = "480";
const blob = new Blob([this.svgOutHTML], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
objElement.data = url;
return new Promise((resolve) => {
objElement.addEventListener('load', () => {
this.svgElement = objElement.contentDocument.querySelector('svg');
this.etboard = this.svgElement.querySelector('.etboard');
this.etdrop = this.svgElement.querySelector('.etdrop');
this.startDot = this.svgElement.querySelector('#ejceesstartdot');
if (this.startDot) {
this.initialDotCx = this.startDot.getAttribute('cx') || '0';
this.initialDotCy = this.startDot.getAttribute('cy') || '0';
}
resolve();
});
svgContainer.appendChild(objElement);
});
} else {
this.svgElement = this.container.querySelector('.ejceespb');
this.etboard = this.container.querySelector('.etboard');
this.startDot = this.container.querySelector('#ejceesstartdot');
if (this.startDot) {
this.initialDotCx = this.startDot.getAttribute('cx') || '0';
this.initialDotCy = this.startDot.getAttribute('cy') || '0';
}
this.etdrop = this.container.querySelector('.etdrop');
return Promise.resolve();
}
}
```
### 5. Update `bindEvents()`
Disable keyboard shortcuts and bind the mini-mode autoplay button.
```javascript
// Inside bindEvents(), add the return clause at the top of the keydown listener
document.addEventListener('keydown', e => {
if (this.mode === 'ejceesmini') return; // Disable keyboard shortcuts for ejceesmini
// ... [Rest of original keydown handling] ...
// At the end of bindEvents(), add the autoplay listener for mini mode
if (this.mode === 'ejceesmini') {
const autoBtn = this.container.querySelector('#btn-autoplay');
if (autoBtn) {
autoBtn.addEventListener('click', () => {
if (this.isAutoPlaying) {
this.stopAutoPlay();
} else {
this.startAutoPlay();
}
});
}
}
```
### 6. Override Interface Methods
Update the `restoreOutputText`, `renderNoteUI`, and `renderRecordUI` methods to behave correctly when running in `ejceesmini` mode.
```javascript
// Update restoreOutputText method
restoreOutputText() {
if (this.mode === 'ejceesmini') {
const totalDepth = this.getPathDepth(this.historyFEN, this.currentBranch) - 1;
this.setOutputText(`${this.currentStepIndex}/${totalDepth}`, false);
return;
}
// ... [Keep original restoreOutputText logic] ...
// Update renderNoteUI method
renderNoteUI() {
if (this.mode === 'ejceesmini') {
const node = this.getNodeAtStep(this.currentStepIndex);
const commentContainer = this.container.querySelector('#mini-comment-container');
if (node && node.c && node.c.trim() !== '') {
commentContainer.textContent = node.c.trim();
commentContainer.style.display = 'block';
} else {
commentContainer.style.display = 'none';
}
const autoBtnSvg = this.container.querySelector('#btn-autoplay svg');
if (autoBtnSvg) {
autoBtnSvg.innerHTML = this.isAutoPlaying
? '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>'
: '<path d="M8 5v14l11-7z"/>';
autoBtnSvg.style.fill = this.isAutoPlaying ? '#fff' : '#ccc';
}
return;
}
// ... [Keep original renderNoteUI logic] ...
// Update renderRecordUI method
renderRecordUI() {
if (this.mode === 'ejceesmini') {
const totalDepth = this.getPathDepth(this.historyFEN, this.currentBranch);
this.stepSlider.max = Math.max(0, totalDepth - 1);
const branchContainer = this.container.querySelector('#mini-branch-container');
const parentNode = this.getNodeAtStep(this.currentStepIndex - 1);
if (parentNode && parentNode.v && parentNode.v.length > 1) {
branchContainer.style.display = 'block';
let forkIndex = -1;
let tempNode = this.historyFEN;
for (let j = 0; j < this.currentStepIndex - 1; j++) {
if (tempNode.v && tempNode.v.length > 1) forkIndex++;
const choice = tempNode.v && tempNode.v.length > 1 ? this.currentBranch[forkIndex] || 0 : 0;
tempNode = tempNode.v[choice];
}
forkIndex++;
const currentChoice = this.currentBranch[forkIndex] !== undefined ? this.currentBranch[forkIndex] : 0;
branchContainer.textContent = `Branch: ${currentChoice + 1}/${parentNode.v.length}`;
branchContainer.onclick = (e) => {
e.stopPropagation();
this.showBranchMenu(branchContainer, parentNode, forkIndex, this.currentStepIndex);
};
} else {
branchContainer.style.display = 'none';
}
this.updateToolHighlights();
return;
}
// ... [Keep original renderRecordUI logic] ...
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
因為div.ejcees本身是container,所以當mode=ejceessingle或ejceesmini時,要在div.ejcees內加一層div.ejceesInner,並根據.ejcees寬度,改變div.ejceesInner的樣式。
只需給出所要修改的地方,所有的代碼都要使用英文。
gemin fast:
只需給出所要修改的地方,所有的代碼都要使用英文。
gemin fast:
代码: 全选
這是一個非常準確的觀察。在 CSS Container Queries 中,**一個元素不能根據自己的 `container-type` 來改變自己的樣式**(這會導致死循環)。必須對**子元素**進行查詢。
以下是針對 `renderDOM`、`injectCSS` 以及 `cacheDOM` 的修改方案:
### 1. 修改 `renderDOM()`
在 `.ejcees` 內部包裝一層 `.ejcees-inner`,並將所有原本直接放在 `.ejcees` 下的內容移入其中。
```javascript
renderDOM() {
// ... [Existing svgOutHTML logic] ...
// Use a wrapper div.ejcees-inner to allow Container Queries to work
if (this.mode === 'ejceesmini') {
this.container.innerHTML = `
<div class="ejcees-inner">
<div class="ejceessvg" style="position: relative;">
<div id="mini-branch-container" class="ejceesmini-branch-menu" style="display: none;"></div>
<div id="mini-comment-container" class="ejceesmini-comment" style="display: none;"></div>
</div>
<div class="ejceesctrl" style="flex: 0 0 auto; height: 48px;">
<div class="ejceesstep">
<div class="ejceesoutput" style="width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; font-size: 12px; padding: 0;">0/0</div>
<div class="ejceesstepminus">-</div>
<input type="range" class="ejceesstepdrop" min="0" max="0" value="0" />
<div class="ejceesstepplus">+</div>
<div class="note-btn" id="btn-autoplay" title="Auto Play" style="width: 36px; height: 36px; cursor: pointer; display: flex; align-items: center; justify-content: center;">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px; fill: #ccc;"><path d="M8 5v14l11-7z"/></svg>
</div>
</div>
</div>
</div>
<input type="file" id="file-input" style="display: none" />`;
} else {
this.container.innerHTML = `
<div class="ejcees-inner">
<div class="ejceessvg">${this.svgOutHTML}</div>
<div class="ejceesctrl">
<div class="ejceesoutput">Click on the SVG area to get coordinates.</div>
<div class="ejceesstep">
<div class="ejceesstepminus">-</div>
<input type="range" class="ejceesstepdrop" min="0" max="0" value="0" />
<div class="ejceesstepplus">+</div>
</div>
<div class="ejceestool"></div>
</div>
</div>
<input type="file" id="file-input" style="display: none" />`;
}
}
```
### 2. 修改 `injectCSS()`
將原本針對 `.ejcees` 的佈局屬性(如 `flex-direction`)轉移到 `.ejcees-inner` 上。
```javascript
if (this.mode === 'ejceessingle' || this.mode === 'ejceesmini') {
resetAndContainerCSS = `
.ejcees {
container-type: inline-size;
width: 100%;
height: ${this.mode === 'ejceesmini' ? 'auto' : '100vh'};
overflow: hidden;
}
/* The inner element responds to the .ejcees container size */
.ejcees-inner {
display: flex;
width: 100%;
height: 100%;
background-color: #f4f4f9;
}
@container (max-width: 767px) {
.ejcees-inner { flex-direction: column; }
.ejceesctrl { flex: 1 0 272px; min-height: 272px; }
.ejceessvg { width: 100%; height: auto; max-height: calc(100cqw * 480 / 432); }
}
@container (min-width: 768px) {
.ejcees-inner { flex-direction: row; }
.ejceesctrl { flex: 1 0 414px; min-width: 414px; }
.ejceessvg { height: 100%; width: auto; max-width: calc(100cqh * 432 / 480); }
}
/* ... existing ejceesmini-specific classes ... */
`;
}
```
### 3. 修改 `cacheDOM()`
由於現在多了一層 `ejcees-inner`,原本的選擇器路徑仍然有效(因為 `querySelector` 是深度查找),但為了確保獲取正確的元素,建議保持如下結構:
```javascript
cacheDOM() {
// These will search inside .ejcees and find elements within .ejcees-inner
this.outputElement = this.container.querySelector('.ejceesoutput');
this.stepSlider = this.container.querySelector('.ejceesstepdrop');
if (this.mode === 'ejceesmini') {
const svgContainer = this.container.querySelector('.ejceessvg');
// ... [Existing <object> creation and Promise logic] ...
// No change needed here as long as .ejceessvg exists inside .ejcees-inner
}
// ...
}
```
### 關鍵點:
1. **`100cqw` 代替 `100vw`**:在 `@container` 查詢中,使用 `cqw` 能確保寬度是相對於父容器 `.ejcees` 的,而不是整個屏幕。
2. **`px` 單位**:在 `@container (min-width: 768px)` 中必須加上 `px`,否則 CSS 會失效。
3. **層級關係**:`.ejcees` (Container) -> `.ejcees-inner` (Target)。這樣 `.ejcees-inner` 就能根據 `.ejcees` 的寬度切換 `flex-direction`。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
container生效的例子:
gemini fast :
gemini fast :
代码: 全选
這裡提供一個完整的 HTML 範例。這個例子展示了為什麼需要「兩層」結構:外層作為 **Container(容器)**,內層作為 **Target(目標)**。
在這個範例中,你可以透過拖動滑塊(Slider)來改變容器的寬度,觀察內部的佈局如何從橫向(Row)切換為縱向(Column),以及字體顏色的變化。
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Container Queries Example</title>
<style>
/* 1. The Container: This element defines the containment context */
.ejcees {
container-type: inline-size;
container-name: chess-container;
width: 80%; /* Initial width */
margin: 20px auto;
border: 3px dashed #333;
background-color: #eee;
overflow: hidden;
resize: horizontal; /* Allows manual resizing in some browsers */
}
/* 2. The Inner Wrapper (Target): This element responds to the container's size */
.ejcees-inner {
display: flex;
gap: 20px;
padding: 20px;
background-color: #f4f4f9;
transition: all 0.3s ease;
}
.ejceessvg {
flex: 1;
background-color: #2c3e50;
color: white;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.ejceesctrl {
flex: 1;
background-color: #ecf0f1;
padding: 15px;
border: 1px solid #bdc3c7;
}
/* 3. Container Queries */
/* Mobile layout: when container width is less than 500px */
@container chess-container (max-width: 499px) {
.ejcees-inner {
flex-direction: column;
background-color: #fff3e0; /* Orange tint for mobile */
}
.ejceessvg {
height: 100px;
}
.ejceessvg::after {
content: " (Mobile View)";
}
}
/* Desktop layout: when container width is 500px or more */
@container chess-container (min-width: 500px) {
.ejcees-inner {
flex-direction: row;
background-color: #e3f2fd; /* Blue tint for desktop */
}
.ejceessvg::after {
content: " (Desktop View)";
}
}
/* Control Panel for Demo */
.demo-controls {
text-align: center;
margin-top: 20px;
font-family: sans-serif;
}
</style>
</head>
<body>
<div class="demo-controls">
<h2>Container Query Demo</h2>
<p>Drag the slider to change the <b>Container's</b> width.<br>
The layout changes based on the container size, NOT the screen size.</p>
<input type="range" id="widthSlider" min="300" max="800" value="600" style="width: 300px;">
<span id="widthValue">600px</span>
</div>
<div class="ejcees" id="mainContainer" style="width: 600px;">
<div class="ejcees-inner">
<div class="ejceessvg">
SVG AREA
</div>
<div class="ejceesctrl">
<h3>Control Panel</h3>
<p>This side panel moves below the SVG when the container is narrow.</p>
</div>
</div>
</div>
<script>
const slider = document.getElementById('widthSlider');
const container = document.getElementById('mainContainer');
const widthDisplay = document.getElementById('widthValue');
slider.addEventListener('input', (e) => {
const val = e.target.value;
container.style.width = val + 'px';
widthDisplay.textContent = val + 'px';
});
</script>
</body>
</html>
```
### 為什麼這個例子會生效?
1. **層級關係**:`@container` 寫在 `.ejcees-inner` 上,它查詢的是父元素 `.ejcees`。這避開了「自查自糾」的錯誤。
2. **明確單位**:使用了 `px`(例如 `min-width: 500px`)。
3. **容器類型**:`.ejcees` 設置了 `container-type: inline-size;`,這告訴瀏覽器該元素需要監聽其寬度變化。
4. **容器命名(選用)**:使用了 `container-name: chess-container;` 並在查詢中使用 `@container chess-container ...`,這在一個頁面有多個容器時能確保查詢精確。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 將製作ejcees(中國象棋打譜程式)
mini的樣式需要修改:
- 附件
-
ejceesobj202604271228.js- (277.54 KiB) 已下载 39 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
在线用户
正浏览此版面之用户: 没有注册用户 和 1 访客
