65.9K
CodeProject 正在变化。 阅读更多。
Home

HTML5 为什么?为什么不? UI / UX 对比存储挑战(N 部分之 2)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2021年12月29日

CPOL

6分钟阅读

viewsIcon

14204

downloadIcon

117

通过上下文菜单和复制到剪贴板讨论 UX。

注意:此软件版本位于名为 updateUI 的分支中。

您也可以在我的 CodePen.io 上尝试该代码 => https://codepen.io/raddevus/pen/eYGVgVg[^]。

引言

阅读本系列第一部分: HTML5 为什么?为什么不? UI / UX 对比存储挑战(N 部分之 1)[^]

背景

我决定在我们的这个小程序中接下来需要的是一种删除条目的方法。

这打开了与 UI / UX 相关的一个大麻烦。我决定创建一种用户体验,感觉就像桌面应用程序一样,所以我添加了一个右键单击项目时出现的上下文菜单(参见下一张图)。但是,这并不适用于移动应用程序,所以将来我必须为移动设备提供一种方法来做到这一点。

两个主要想法

在本文中,我想谈论两件事:

  1. 我如何构建 ContextMenu & 它的工作原理
  2. 通过 Web 应用程序复制到剪贴板 -- 它不必要地棘手

Web 应用程序中的上下文菜单

当用户右键单击用户界面中的某个元素时显示的上下文菜单是一项基本人权。多年来,我们一直通过这种方式与桌面应用程序进行交互,而且它非常直观。当然,移动应用程序不需要指向设备,实际上只接受等同于鼠标左键单击的触摸命令。移动应用程序没有右键单击的概念,因此不提供上下文菜单。这很遗憾。

构建您自己的上下文菜单

这也意味着,为了提供 ContextMenu 的简单且直观的功能,您必须自己构建它。要做到这一点,您必须了解 CSS 样式和浏览器事件。

Bootstrap 样式

为了帮助我完成 ContextMenu 的图形设计,我采用了最新版本的 Bootstrap[^]。

这为我提供了一些漂亮的样式,使我的上下文菜单模仿 Windows 中的上下文菜单样式。

然而,它并没有为上下文菜单提供任何功能。为此,我们将需要我们老朋友 JavaScript。

HTML 看起来是这样的

<div class="RADcontextMenu card border border-primary">
 <div class="card-body">
 <ul>
  <li onclick="onContextMenuClick(this)" id="delete_item" 
      class="contextMenuItem">Delete item</li>
  <li onclick="onContextMenuClick(this)" id="copy_address" 
      class="contextMenuItem">Copy image address</li>
  <li onclick="onContextMenuClick(this)" id="other_thing" 
      class="contextMenuItem">Do other thing</li>
  <li onclick="onContextMenuClick(this)" id="more_stuff" 
      class="contextMenuItem">Do more stuff</li>
 </ul>
 </div>
 </div>

它只是一个普通的 div 标签和一个无序列表项。列表中的每个项目都是一个出现并可单击的菜单项。每个菜单项都会触发相同的 JavaScript 方法 (onContextMenuClick()),该方法传入被单击的 HTML 元素。

通过该元素,我们将获取相关的 ID 值,以便轻松确定应触发哪个上下文菜单逻辑。

HTML 类名

其中许多类名(用于应用 CSS 样式)来自 Bootstrap。

cardborderborder-primarycard-body 都来自 Bootstrap 样式。

我创建的一个样式是主要的 RADcontextMenu 样式。

这是main.css中的所有样式。

用于创建用户界面的 CSS 样式

当用户将鼠标悬停在上下文菜单项上时,背景会变成蓝色,字体会变成白色。

.contextMenuItem:hover {background-color: #4281F4;color:white; }

主上下文菜单从视图中隐藏(visibility:hidden; display:none;),直到用户右键单击其中一个图像。

.RADcontextMenu {z-index:1000;
                    visibility:hidden;
                    display:none;
                    width:200px;
                    height:100px;
                    position:absolute;
                    font-family:'Microsoft Sans Serif';
                    font-size:11px;
    }

当用户右键单击其中一个图像时,上下文菜单就会出现,以下是处理该问题的代码:

function drawContextMenu()
{
  isContextMenuDisplayed = true;
  console.log('drawContextMenu : ' + new Date())
  //console.log($('.EScontextMenu').text());
  document.querySelector('.RADcontextMenu').style.visibility = "visible";
  document.querySelector('.RADcontextMenu').style.display = "block";
  document.querySelector('.RADcontextMenu').style.top = mousePos.y + "px";
  document.querySelector('.RADcontextMenu').style.left = mousePos.x + "px";
}

我们设置样式,以便上下文菜单可见并显示(块)。

我们还确保上下文菜单的左上角位于用户右键单击的位置(mousePos.x, mousePos.y)。

我们可以使用 mousePos.x & mousePos.y,因为在用户将鼠标移动到页面上的过程中,我们一直在使用以下 JavaScript 函数跟踪鼠标移动。

function onMouseMove(e)
{
  mousePos = { x: e.clientX, y: e.clientY };
}

一旦用户单击上下文菜单中的某个项目,我们就可以通过其 ID 知道单击了哪个项目,我们在 case 语句中使用它。

这是我们正在处理的两个重要上下文菜单项的代码。
注意:这不是 onContextMenuClick(e) 的完整源代码。

function onContextMenuClick(e)
{
  console.log("onContextMenuClick()");
  hideContextMenu();
  
  console.log(e.id);
  switch (e.id)
  {
    case 'delete_item':
      {
        // load all localStorage images into array.
        let allImg = JSON.parse(localStorage.getItem("allImg"));
        // remove targeted item
        allImg.splice(currentHoverImageIdx,1);
        // store updated array to localStorage
        localStorage.setItem("allImg",JSON.stringify(allImg));
        removeMainDiv();
        displayImages();
        break;
      }
    case 'copy_address':
      {
        // ### --- Because of limitations on copying to clipboard 
        // ###     we have to copy from text input.
        // ####    Since we don't want that 
        let clipboard = document.querySelector("#clipboard");
        clipboard.style.visibility = "visible";
        clipboard.style.display = "block";
        clipboard.value=currentImageAddress;
        clipboard.select();
        clipboard.setSelectionRange(0,1000);
        document.execCommand("copy");
        clipboard.style.visibility = "hidden";
        clipboard.style.display = "none";

        break;
      }

删除一个项目

现在,如果用户选择删除某个项目,我们只需:

  1. 将所有图像(URL)从 localStorage 加载到本地数组中
  2. 在数组上调用 JavaScript splice() 方法,按索引删除当前项目
  3. 将所有项目(减去我们刚刚删除的一个)保存回 localStorage
  4. 删除主 div(清空屏幕)
  5. displayImages() - 显示 localStorage 中的所有图像。

复制图像 URL

我们还希望用户能够轻松获取图像 URL。

我想将图像 URL 复制到用户的剪贴板。由于浏览器奇怪的功能,在 Web 应用程序中从剪贴板复制一直是一项奇怪而艰巨的任务。某些开发人员似乎认为复制到剪贴板是一项危险的活动。我不确定为什么。我可以看到从剪贴板复制可能很危险,但反之则不然。

复制到剪贴板的解决方法

在这种情况下,为了使其跨浏览器/跨平台工作,我找到了一种解决方法。

我有一个隐藏的输入文本框,我将其命名为 clipboard

我将该文本框的文本值设置为图像 URL 的值。

接下来,我选择文本框中的所有文本: clipboard.setSelectionRange(0,1000);

文档有一个特殊的命令称为 execCommand(),它根据您想做的事情接受不同的参数值。它允许我们将内容复制到用户的剪贴板,并复制最后选择的内容,所以我们现在调用该方法: document.execCommand("copy");

执行此操作后,我们立即再次隐藏剪贴板输入文本框,用户永远不会看到它发生。

现在图像 URL 已复制到剪贴板,用户可以将其粘贴到任何地方。

RFC:有更好的方法复制到用户剪贴板吗?

征求意见书 - 如果您知道更好的复制到剪贴板的方法,请告诉我。

我对此进行了相当广泛的研究,并且有许多产品,但很少有能到处都起作用的。

摘要

做在 WinForm 开发时代自动完成的事情,现在需要大量的努力。

现在我们必须自己进行控件样式设置和事件处理,尽管在 Visual Studio 的 WinForm 开发中,这已经变得非常容易了。

所有这些只是为了获得一个好的 UX(用户体验),即直观的界面。

展望未来与数据共享

然而,这一切都无助于使我们的数据更容易访问。我将研究一些方法来做到这一点,我们将在下一篇文章中介绍。

历史

  • 2021年12月29日:首次发布
© . All rights reserved.