使用Javascript在本地保存HTML

Bas*_*asj 21 html javascript serialization

我知道,出于安全原因,Javascript无法在文件系统中写入数据.我经常读到,用Javascript本地保存数据的唯一方法是使用cookies或localStorage.

但它是可能的,在非常特殊的情况下,当文件被访问本地(通过互联网而不是),将数据写入本地?(没有任何服务器语言,甚至根本没有任何服务器......只是本地浏览HTML文件)

例如,可以通过点击此页面上的SAVE实现 ...

... HTML文件被新内容覆盖了吗?(即按下SAVE时应更新本地 HTML文件).


在此输入图像描述


感谢Javascript,当本地访问HTML页面时,这可以保存文件吗?

注意:我希望能够以静默方式保存,而不是建议下载/保存对话框,用户必须在其中选择下载位置,然后"确定要覆盖"等.


编辑:为什么这个问题?我希望能够做一个HTML/JS记事本,我可以在没有任何服务器的情况下在本地运行(没有apache,没有php).我需要能够轻松保存,而无需处理对话框"你想在哪里下载文件?".

Awe*_*s01 13

你可以使用Blob函数:

function save() {
  var htmlContent = ["your-content-here"];
  var bl = new Blob(htmlContent, {type: "text/html"});
  var a = document.createElement("a");
  a.href = URL.createObjectURL(bl);
  a.download = "your-download-name-here.html";
  a.hidden = true;
  document.body.appendChild(a);
  a.innerHTML = "something random - nobody will see this, it doesn't matter what you put here";
  a.click();
}
Run Code Online (Sandbox Code Playgroud)

并且您的文件将保存.

  • 最好添加一个 htmlContent 的示例。`var htmlContent = [ "<head><meta charset='utf-8'><title>测试</title></head>", "<style>.container{max-width: 940px;margin: 0 auto ;}</style>", "<body><div class="container">'此处内容'</div></body>" ];` (2认同)
  • 仍然要求我确认下载。 (2认同)

Ste*_*eve 8

Chromium 的文件系统访问 API(2019 年推出)

有一个相对较新的非标准文件系统访问 API(不要与早期的文件和目录条目 API文件系统 API混淆)。它似乎是在 2019/2020 年在 Chromium/Chrome 中引入的,并且在 Firefox 或 Safari 中不支持。

使用此API时,本地打开的页面可以打开/保存其他本地文件并在页面中使用文件的数据。它确实需要初始权限才能保存,但当用户位于页面上时,特定文件的后续保存会“静默”进行。用户还可以授予对特定目录的权限,随后对该目录的读取和写入不需要批准。用户关闭网页的所有选项卡并重新打开页面后,需要再次批准。

您可以在https://web.dev/file-system-access/阅读有关这个​​新 API 的更多信息。它旨在用于制作更强大的 Web 应用程序。

关于它有几点需要注意:

  • 默认情况下,它需要安全上下文才能运行。在 https、localhost 或通过 file:// 运行它应该可以工作。

  • 您可以使用DataTransferItem通过拖放文件来获取文件句柄.getAsFileSystemHandle

  • 最初读取或保存文件需要用户批准,并且只能通过用户交互启动。此后,后续读取和保存不需要批准,直到再次打开该站点。

    在此输入图像描述

  • 文件句柄可以保存在页面中(因此,如果您正在编辑本地文件'/path/to/file.txt'并重新加载页面,它将能够引用该文件)。它们似乎无法被字符串化,因此通过 IndexedDB 之类的东西存储(有关更多信息,请参阅此答案)。使用存储的句柄进行读/写需要用户交互和用户批准。

以下是一些简单的例子。它们似乎不能在跨域 iframe 中运行,因此您可能需要将它们另存为 html 文件并在 Chrome/Chromium 中打开它们。

通过拖放打开和保存(无外部库):

<body>
<div><button id="open">Open</button><button id="save">Save</button></div>
<textarea id="editor" rows=10 cols=40></textarea>
<script>
let openButton = document.getElementById('open');
let saveButton = document.getElementById('save');
let editor = document.getElementById('editor');
let fileHandle;
async function openFile() {
  try {
    [fileHandle] = await window.showOpenFilePicker();
    await restoreFromFile(fileHandle);
  } catch (e) {
    // might be user canceled
  }
}
async function restoreFromFile() {
  let file = await fileHandle.getFile();
  let text = await file.text();
  editor.value = text;
}
async function saveFile() {
  var saveValue = editor.value;
  if (!fileHandle) {
    try {
      fileHandle = await window.showSaveFilePicker();
    } catch (e) {
      // might be user canceled
    }
  }
  if (!fileHandle || !await verifyPermissions(fileHandle)) {
    return;
  }
  let writableStream = await fileHandle.createWritable();
  await writableStream.write(saveValue);
  await writableStream.close();
}

async function verifyPermissions(handle) {
  if (await handle.queryPermission({ mode: 'readwrite' }) === 'granted') {
    return true;
  }
  if (await handle.requestPermission({ mode: 'readwrite' }) === 'granted') {
    return true;
  }
  return false;
}
document.body.addEventListener('dragover', function (e) {
  e.preventDefault();
});
document.body.addEventListener('drop', async function (e) {
  e.preventDefault();
  for (const item of e.dataTransfer.items) {
    if (item.kind === 'file') {
      let entry = await item.getAsFileSystemHandle();
      if (entry.kind === 'file') {
        fileHandle = entry;
        restoreFromFile();
      } else if (entry.kind === 'directory') {
        // handle directory
      }
    }
  }
});
openButton.addEventListener('click', openFile);
saveButton.addEventListener('click', saveFile);
</script>
</body>
Run Code Online (Sandbox Code Playgroud)

使用idb-keyval存储和检索文件句柄:

存储文件句柄可能很棘手,因为它们不能取消字符串化,尽管显然它们可以与 IndexedDB 一起使用,并且大多数与history.state. 在本示例中,我们将使用idb-keyval访问 IndexedDB 来存储文件句柄。要查看其工作情况,请打开或保存文件,然后重新加载页面并按“恢复”按钮。此示例使用/sf/answers/4615723731/中的一些代码。

<body>
<script src="https://unpkg.com/idb-keyval@6.1.0/dist/umd.js"></script>
<div><button id="restore" style="display:none">Restore</button><button id="open">Open</button><button id="save">Save</button></div>
<textarea id="editor" rows=10 cols=40></textarea>
<script>
let restoreButton = document.getElementById('restore');
let openButton = document.getElementById('open');
let saveButton = document.getElementById('save');
let editor = document.getElementById('editor');
let fileHandle;
async function openFile() {
  try {
    [fileHandle] = await window.showOpenFilePicker();
    await restoreFromFile(fileHandle);
  } catch (e) {
    // might be user canceled
  }
}
async function restoreFromFile() {
  let file = await fileHandle.getFile();
  let text = await file.text();
  await idbKeyval.set('file', fileHandle);
  editor.value = text;  
  restoreButton.style.display = 'none';
}
async function saveFile() {
  var saveValue = editor.value;
  if (!fileHandle) {
    try {
      fileHandle = await window.showSaveFilePicker();
      await idbKeyval.set('file', fileHandle);
    } catch (e) {
      // might be user canceled
    }
  }
  if (!fileHandle || !await verifyPermissions(fileHandle)) {
    return;
  }
  let writableStream = await fileHandle.createWritable();
  await writableStream.write(saveValue);
  await writableStream.close();
  restoreButton.style.display = 'none';
}

async function verifyPermissions(handle) {
  if (await handle.queryPermission({ mode: 'readwrite' }) === 'granted') {
    return true;
  }
  if (await handle.requestPermission({ mode: 'readwrite' }) === 'granted') {
    return true;
  }
  return false;
}
async function init() {
  var previousFileHandle = await idbKeyval.get('file');
  if (previousFileHandle) {
    restoreButton.style.display = 'inline-block';
    restoreButton.addEventListener('click', async function (e) {
      if (await verifyPermissions(previousFileHandle)) {
        fileHandle = previousFileHandle;
        await restoreFromFile();
      }
    });
  }
  document.body.addEventListener('dragover', function (e) {
    e.preventDefault();
  });
  document.body.addEventListener('drop', async function (e) {
    e.preventDefault();
    for (const item of e.dataTransfer.items) {
      console.log(item);
      if (item.kind === 'file') {
        let entry = await item.getAsFileSystemHandle();
        if (entry.kind === 'file') {
          fileHandle = entry;
          restoreFromFile();
        } else if (entry.kind === 'directory') {
          // handle directory
        }
      }
    }
  });
  openButton.addEventListener('click', openFile);
  saveButton.addEventListener('click', saveFile);
}
init();
</script>
</body>
Run Code Online (Sandbox Code Playgroud)

补充笔记

Firefox 和 Safari 支持似乎不太可能,至少在短期内是这样。请参阅https://github.com/mozilla/standards-positions/issues/154https://lists.webkit.org/pipermail/webkit-dev/2020-August/031362.html

  • @Basj,应该可以通过[`DataTransferItem.getAsFileSystemHandle`](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/getAsFileSystemHandle)(并且[此处](https: //web.dev/file-system-access/#drag-and-drop-integration))。当我有机会测试时,我会更新我的答案。 (2认同)

Fly*_*ter 7

来自W3C File API标准的规范答案:

用户代理应提供暴露给脚本的API,以暴露上述功能.无论何时发生与文件系统的交互,UI都会通知用户,从而使用户能够完全取消或中止交易.用户会收到任何文件选择的通知,并可以取消这些选择.在没有用户干预的情况下,不会对这些API进行无提示调用.

基本上,由于安全设置,每次下载文件时,浏览器都会确保用户确实想要保存文件.浏览器并没有真正区分计算机上的JavaScript和Web服务器上的JavaScript.唯一的区别是浏览器如何访问文件,因此在本地存储页面不会产生任何影响.

解决方法: 但是,您可以将<div>cookie 的innerHTML存储起来.当用户返回时,您可以从cookie中加载它.虽然它不是将文件保存到用户的计算机上,但它应该与覆盖文件具有相同的效果.当用户返回时,他们将看到他们上次输入的内容.缺点是,如果用户清除他们的网站数据,他们的信息将丢失.由于忽略用户清除本地存储的请求也是一个安全问题,因此实际上无法绕过它.

但是,您也可以执行以下操作:

  • 使用Java小程序
  • 使用其他类型的小程序
  • 创建桌面(非基于Web)应用程序
  • 请记住在清除网站数据时保存文件.退出页面时,您可以创建弹出并提醒您的警报,甚至可以打开保存窗口.

使用cookie:可以在本地页面上使用JavaScript cookie.只需将其放入文件并在浏览器中打开即可:

<!DOCTYPE html>
<html>

<head>
</head>

<body>
  <p id="timesVisited"></p>
  <script type="text/javascript">
    var timesVisited = parseInt(document.cookie.split("=")[1]);
    if (isNaN(timesVisited)) timesVisited = 0;
    timesVisited++;
    document.cookie = "timesVisited=" + timesVisited;
    document.getElementById("timesVisited").innerHTML = "You ran this snippet " + timesVisited + " times.";
  </script>
</body>

</html>
Run Code Online (Sandbox Code Playgroud)