本周,英国与世界上许多其他地区一起对日冕病毒(AKA Covid-19)进行了封锁,因此,至少可以说,我们处于一种非常奇怪的状况。 希望大家都保持安全,并照顾好自己和亲人。 我为那些受到个人影响的人,以及前线令人难以置信的NHS工人感到振奋,他们每天都在战斗,冒着自己的健康来保证我们的安全。
就我个人而言,我发现应对这种巨大动荡的最佳方法是控制我所能做的小事情。 作为父母,我和我的丈夫以及其他许多人一样,由于儿子的学前班已经关闭,我发现自己在家上班时在玩弄育儿。 我知道他会错过他的学前班,而随着我们的活动范围被严重缩减,现在是时候让我们变得有创意了,并提出一些有趣的游戏和在家里进行的活动的想法。 我决定制定一个工作日时间表,让我们每个星期都参加一些活动,以保持一些日常工作。 而且,仅出于娱乐目的(并出于额外的动机!),我将其设置为交互式Web时间表。
尽管这只是一个有趣的小项目,但在此过程中仍有一些有趣的决定。 我最喜欢的项目类型是我可以解决问题和使用约束的地方。 因此,让我们看一下制定时间表的一些实际考虑因素,以及它们如何影响结果。
设计
我设计的时间表看起来有点像白板,上面有我们要用来填充的活动的便笺。 首先将它们排成一行,然后当您单击按钮时,活动将随机化(以使事情变得有趣!),并移至时间表本身。
如您所料,时间表的顶部是星期几(作为列标题),左侧是一天中的几小时。 为了使生活更轻松,我们将每天按小时划分,并假设每个活动持续一个小时(或大约一个小时)。 有许多预先填充的时间段-例如,早餐和午餐每天在同一时间发生。
表格和网格
在任何Web项目中,首先要考虑的是语义HTML。 如果您以前曾浏览过此博客,则可能已经意识到我是CSS Grid的忠实拥护者 ,并且它通常是我选择的布局方法。 但是在这种情况下,我们要建立一个表 。 CSS Grid不是构建数据表的正确选择。
HTML <table>
元素在正确使用时可以为我们提供实现此目的所需的所有语义和布局。 要将表格构建为网格,我们需要使标记变平,删除行标题和列标题与内容的关系。 不用说,这将对可访问性造成灾难。
您不应该执行的操作是在<table>
上放置display: grid
。 这使屏幕阅读器完全无法访问该表。 乍一看,您可能仍然认为这是荒谬的:网格项只能是网格容器的直接子级,而表标记则需要嵌套元素( <tbody>
, <tr>
, <td>
等)。 但是Subgrid(属于CSS Grid Level 2规范的一部分 ,并且目前仅在Firefox中受支持)使其更具吸引力。 Subgrid允许网格项目继承父网格-因此,您可以具有多个层次的后代元素,这些元素均与公共祖先的网格对齐。 一般而言,它对于布局而言功能强大,但不应在<table>
元素上使用。 display
属性是那些更改HTML语义的属性之一。 只是不要这样做!
我确实做了,但是使用Grid作为主要布局和活动列表,这很好。
如果您想了解有关Subgrid的更多信息, Rachel Andrew对此进行了撰写和讨论。 本文是一个不错的起点。
构建表格标记
现在,我们已经决定使用<table>
,我们可以在填充它之前添加基本标记(该部分需要Javascript)。 我们需要确保在单击按钮时仅填充空表单元格。 我们可以寻找只空单元与JS -是这样的:
const cells =[... document .querySelectorAll("td")];
cells .filter(el=> el . innerText ==="");
但是,一旦我们填充了时间表,这些单元格就不再是空的,因此如果我们想再次对活动进行排序,那将是行不通的。 相反,我们可以在要定位的单元格上使用data- *属性 。 此示例显示了两个表行,第一行包含空单元格(具有data- *属性),第二行包含预填充的内容。 ( <span>
元素仅用于样式设置。)
< tr>
< thscope=" row "> 11:00am </ th>
< tddata-cell></ td>
< tddata-cell></ td>
< tddata-cell></ td>
< tddata-cell></ td>
< tddata-cell></ td>
</ tr>
< tr>
< thscope=" row "> 12:00 </ th>
< td>< span> Lunch </ span></ td>
< td>< span> Lunch </ span></ td>
< td>< span> Lunch </ span></ td>
< td>< span> Lunch </ span></ td>
< td>< span> Lunch </ span></ td>
</ tr>
现在我们可以只选择需要填充的单元格:
const cells =[... document .querySelectorAll("[data-cell]")];
(如果需要,我们可以为此使用类名而不是data- *属性。)
CSS
活动的样式看起来像便利贴。 每种颜色的随机颜色和轻微旋转都会增加这种错觉。 我们可以使用nth-child
或nth-of-type
伪选择器设置样式,从一开始就可以正常工作。 但是,当活动在时间表上随机排列后,这种样式方法就会失效。 最初的第一个孩子现在不再是–以前是粉红色的便利贴在添加到板上时可能会变成绿色。
我们希望每个活动在移动时都保持其原始样式。 我选择解决此问题的方法是在每个项目HTML中使用内联的自定义属性。
< divclass=" activity " style="--bg: #adff8a ;--r: 1.25deg ;"> Outdoor games </ div>
通过在CSS中设置默认值,仅当我们希望自定义属性值不同于标准值时,才需要更改它们:
.activity{
background-color:var( --bg , #fcf3b8 );
transform:rotate(var( --r , -2deg ));
}
与JS互动
为了简单起见,我们将假设我们的可能活动列表与可用单元格的数量完全匹配。 这样,我们只需要洒一些JS就可以实现所需的交互性。
当我们单击一个按钮时,我们希望发生以下情况:
- 创建一个包含我们的活动的新数组(每个活动均作为HTML元素)。
- 将数组随机排列。
- 循环遍历空表单元格,并将数组中HTML元素添加到其中。
- 删除活动的原始列表(或从视图中隐藏它们)。
我不想重新发明轮子(也是,我很懒),所以使用Internet的shuffle
函数来随机排列数组项:
constshuffle=array=>{
let a =[... array ];
for(let i = a . length -1; i >0; i --){
const j = Math .floor( Math .random()*( i +1));
[ a [ i ], a [ j ]]=[ a [ j ], a [ i ]];
}
return a ;
};
我在循环使用表格单元格中forEach
方法,主要是因为我讨厌写作for
循环。 首先,我要删除单元格中已有的所有内容(以防我们已经填充了内容),然后从数组中获取HTML元素并将其插入到单元格中。 我们可以使用带有相应索引的索引,以确保我们不添加重复项。
cells.forEach((el , i)=>{
el . innerHTML ="";
if( i < activities . length ){
el . innerHTML =`${ activitiesList [ i ]. innerHTML }`;
}
});
然后,通过设置display
属性隐藏原始数组:
listEl. style . display ="none";
我们可以将所有这些放到一个函数中:
constsortActivities=()=>{
const activitiesList =shuffle( activities );
cells .forEach((el , i)=>{
el . innerHTML ="";
if( i < activities . length ){
el . innerHTML =`${ activitiesList [ i ]. innerHTML }`;
}
});
listEl . style . display ="none";
};
我们可以采用许多不同的方法来解决这个问题,但我不会宣称这绝对是最有效的方法,但是我的目标是“快速而简单”!
最后,我想添加另一个按钮以清除时间表并重新开始。 这部分非常简单明了–只需删除每个表单元格HTML内容,并恢复原始活动列表的可见性即可:
constclearTimetable=()=>{
cells .forEach((el , i)=>{
el . innerHTML ="";
});
listEl . style . display ="grid";
};
这是完整的演示:
Wrapping up
现在我们有一个简单的交互式小时间表。 尽管我计划很快创建一个真实的版本(我想对于小孩子来说更有趣!),但是这个版本很有趣,可以帮助我激发灵感。 如果您已经阅读了此书,希望您也学到了一些东西。
翻译自: https://css-irl.info/building-an-interactive-timetable/