<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: huozhi</title>
    <description>The latest articles on DEV Community by huozhi (@huozhi).</description>
    <link>https://dev.to/huozhi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F298315%2Fc11990b4-9f05-4cd3-add3-31da90d589fa.jpg</url>
      <title>DEV Community: huozhi</title>
      <link>https://dev.to/huozhi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/huozhi"/>
    <language>en</language>
    <item>
      <title>Convert HTML to Anything You Want!</title>
      <dc:creator>huozhi</dc:creator>
      <pubDate>Sun, 22 Dec 2019 06:47:49 +0000</pubDate>
      <link>https://dev.to/huozhi/convert-html-to-anything-you-want-3fnm</link>
      <guid>https://dev.to/huozhi/convert-html-to-anything-you-want-3fnm</guid>
      <description>&lt;p&gt;If you feel it's too long to read, here's the repo: &lt;a href="https://github.com/huozhi/html2any" rel="noopener noreferrer"&gt;https://github.com/huozhi/html2any&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;There was a task, to create a FAQ site, which is fully functional to provide user with help information.&lt;/p&gt;

&lt;p&gt;Designer: First we have a search bar 🔍, able to retrieve every index page. pages are in rich text.&lt;br&gt;
Dev: Yep, sounds good, not difficult. (I guess markdown can handle all)&lt;br&gt;
Designer: Rich text is required to support videos, gif, inline images, block images blablabla...we hope it could be aligned with our main site, all the theme, animations are same.&lt;br&gt;
Dev: Emmm...this is a new fresh project, can we just use the default video controls?&lt;br&gt;
Designer: The vidos/gif need to be same with main site, the basic controls is not enough for user.&lt;br&gt;
Dev: And where are these contents from?&lt;br&gt;
Designer: Maybe an editor in CMS to publish new pages?&lt;br&gt;
Dev: Hurry?&lt;br&gt;
Designer: Yep! hope be ready soon!&lt;/p&gt;

&lt;p&gt;** WHAT THE HELLLLL... **&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.moegirl.org%2Fcommon%2Fthumb%2F4%2F41%2FNicky.jpg%2F250px-Nicky.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.moegirl.org%2Fcommon%2Fthumb%2F4%2F41%2FNicky.jpg%2F250px-Nicky.jpg" alt="WHAT??"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks impossible to finish this work in a such short time with markdown. However it's insane to hard code all static pages within react or other js code. The point is, the &lt;code&gt;RichText&lt;/code&gt; component in existing project is not able to easily migrate now, and they still have other logics to handle text collapse / metrics collecting...which we don't really need.&lt;/p&gt;

&lt;p&gt;For us, &lt;strong&gt;we just want a static page&lt;/strong&gt;. That's it.&lt;/p&gt;

&lt;p&gt;What I can only decouple from existing project are: Video, Image and Gif components. CMS will always provide me with a HTML string for the rich text content. I have to figure out a way to replace the native image/videoes tag with customized react components.&lt;/p&gt;
&lt;h2&gt;
  
  
  Editor &amp;amp; RichText
&lt;/h2&gt;

&lt;p&gt;While you typing stuff in rich text editor, making them bold / italic, inserting some images, you've already finished once rich text editing. Since these content are not only pure text to display, they need more complicated composing with HTML and CSS, even JavaScript to perform an interaction.&lt;/p&gt;

&lt;p&gt;There are kinds of editors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;stateful editor: such as draftjs, slate.They all convert HTML to a middle state, then serialize from state to final HTML&lt;/li&gt;
&lt;li&gt;non-stateful editor: doesn't need state, maybe only rely on contenteditable, encapsulate on the top, such as Medium.js&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Saving editing content has 2 usual thoughts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use stateful editor, sync state to database. Recover from state in database when you display. Feel natural.&lt;/li&gt;
&lt;li&gt;Use any editor you like. Communicate by HTML between client and storage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Saving state may bring potential traps. For example you want to migrate from google closure editor to draftjs. There isn't any state before, the new comer breaks rules. Make you hard to handle the previous way. Migration takes effort and risks.&lt;/p&gt;

&lt;p&gt;If you saving HTML string with stateful editor, you have to write your own serializer + deserializer state converter. Draft requires lib like draft-convert, slate has built-in serializer and deserializer with convenient usage.&lt;/p&gt;
&lt;h2&gt;
  
  
  Went So Far. Anything Related to Our Stuff?
&lt;/h2&gt;

&lt;p&gt;First tasting on slate editor I felt free because of its &lt;a href="https://docs.slatejs.org/walkthroughs/saving-and-loading-HTML-content.html" rel="noopener noreferrer"&gt;HTML convert&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paragraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;childNodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Add a serializing function property to our rule...&lt;/span&gt;
    &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paragraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Html&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Create a new serializer instance with our `rules` from above.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;htmlString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;htmlString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;someState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isn't it interesting? Enjoy state and HTML switching after you just defined a de/serialization rule. COOL!&lt;/p&gt;

&lt;p&gt;When you reach here, got it? What we need is a thing, without any editor functions, be capable to convert HTML into and from structural state. To help us display complicated state visualization. &lt;/p&gt;

&lt;h2&gt;
  
  
  LETS DOT IT
&lt;/h2&gt;

&lt;p&gt;Still remember the principle of compiler? The process of consuming code string and output as machine code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tokenizer: extract special tokens&lt;/li&gt;
&lt;li&gt;parse: build tokes to AST&lt;/li&gt;
&lt;li&gt;transform: transform AST to dest code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the same, our HTML and state are totally like this process. dest code is our final visual form. It could be a component or a HTML string, even a JSON object, whatever.&lt;/p&gt;

&lt;p&gt;What we're going to do are following 3 steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tokenize HTML into proper HTML tags&lt;/li&gt;
&lt;li&gt;Build a tree, each node is a HTML tag containing its information and children&lt;/li&gt;
&lt;li&gt;Traverse this tree with replacing the node into your own&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduce you html2any
&lt;/h2&gt;

&lt;p&gt;Checkout my final implementation: &lt;a href="https://github.com/huozhi/html2any" rel="noopener noreferrer"&gt;https://github.com/huozhi/html2any&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Run on React Native
&lt;/h3&gt;

&lt;p&gt;Check the presentation on &lt;a href="https://github.com/huozhi/html2any-rn-demo" rel="noopener noreferrer"&gt;React Native&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;A paragraph containing bold fonts and images was converted to the react native form. Here's the screenshot on iOS:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fhuozhi%2Fhtml2any-rn-demo%2Fmaster%2Fpublic%2Fv2any-rn-demo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fhuozhi%2Fhtml2any-rn-demo%2Fmaster%2Fpublic%2Fv2any-rn-demo.png" alt="rn-demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course the component nested rules on React Native is much restrict, e.g. Text need sit under View with size specified. Text under Text doesn't inherit styles, which unlike CSS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run on Web with React
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://huozhi.github.io/html2any-web-demo/" rel="noopener noreferrer"&gt;Click Here!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I made a simple transform rule for web:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;br to hr tag&lt;/li&gt;
&lt;li&gt;replace gif with a gif player including loading phase&lt;/li&gt;
&lt;li&gt;Native video tag change to react video player&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Want more? you can design more complicated rule function, then left it to html2any to handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference and Comparison
&lt;/h2&gt;

&lt;p&gt;Actually we have lots of HTML parser in community. The most familiar ones are parse5 and HTMLparser2. Even cheerio is using HTMLparser2, why create the wheel again?&lt;/p&gt;

&lt;p&gt;My reasons are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;html2any is really small enough. It's worth to try if you want to show any content generated from slate or dratjs.&lt;/li&gt;
&lt;li&gt;many parsers are in sax form, parsing top to down. Creating a few API to handle the middle processing phase. For usage like ours, we don't need that much. And they do much compatible work for unreachable cases.&lt;/li&gt;
&lt;li&gt;The most important reason —— more parsers are for web specially. Their outputs may be DOM tree, that's not our desired dest code. See the examples above right? Our doing is Universal HTML! Render Everywhere! Haha&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My &lt;a href="https://docs.google.com/presentation/d/1GDrZ1dZ8xeCPyy0FSy9lS0KFHhztM2LOlt5pjzvuOs8" rel="noopener noreferrer"&gt;slide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>parser</category>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why Positioning a Tooltip Accurately with React So Complicated?</title>
      <dc:creator>huozhi</dc:creator>
      <pubDate>Sun, 22 Dec 2019 06:45:43 +0000</pubDate>
      <link>https://dev.to/huozhi/why-positioning-a-tooltip-accurately-with-react-so-complicated-oii</link>
      <guid>https://dev.to/huozhi/why-positioning-a-tooltip-accurately-with-react-so-complicated-oii</guid>
      <description>&lt;h2&gt;
  
  
  Positioning Problem
&lt;/h2&gt;

&lt;p&gt;You might heard lots of sayings of positioned component, such as popup, tooltip, popover, overlay...&lt;br&gt;
they have the common way that you need to position it when you trigger it.&lt;/p&gt;

&lt;p&gt;To generalize the problem, thinking deeper, we can encapsulate the components into 2 things: a trigger which you might press or hover in; and a overlay which positioning relatively to the trigger. it might be a tooltip, a popped dialog.&lt;/p&gt;

&lt;p&gt;Since I'm using React.js, so I'm going to design it as a react component to solve my positioning problem, and share it as foundation among the overlay-like components. Pure logic, without any styling.&lt;/p&gt;

&lt;p&gt;Then I came out the basic idea of the API. The single child component is the trigger, the we pass the overlay component as a prop to &lt;code&gt;OverlayTrigger&lt;/code&gt; with the &lt;code&gt;placement&lt;/code&gt; position in string literal. It will get rendered with accurate position once hover or focus on the button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OverlayTrigger&lt;/span&gt;
  &lt;span class="nx"&gt;placement&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;focus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
  &lt;span class="nx"&gt;overlay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;hover&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/OverlayTrigger&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result might look like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F4800338%2F70851736-eeb51900-1ed3-11ea-8ca6-6eea6fdad10b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F4800338%2F70851736-eeb51900-1ed3-11ea-8ca6-6eea6fdad10b.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Gonna It Work?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;We got the the &lt;code&gt;trigger&lt;/code&gt; get mounted on the DOM;&lt;/li&gt;
&lt;li&gt;We mount the &lt;code&gt;overlay&lt;/code&gt; to DOM when we interact with it (hover or focus)&lt;/li&gt;
&lt;li&gt;We position get the position &amp;amp; size by &lt;code&gt;getBoundingClientRect&lt;/code&gt; API of above components, and change the position of overlay to close to trigger with specified placement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;pseudo code like the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// after both get mounted, get the positions and sizes&lt;/span&gt;
  &lt;span class="nx"&gt;overlaySize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;overlayPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSizeAndPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;triggerSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;triggerPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSizeAndPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// move overlay near to the trigger&lt;/span&gt;
  &lt;span class="nf"&gt;rePositionOverlay&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's might also have a root element which you want to hook your overlay on, by default, it's &lt;code&gt;document.body&lt;/code&gt;. &lt;br&gt;
Then you can position it with &lt;code&gt;fixed&lt;/code&gt; or &lt;code&gt;absolute&lt;/code&gt; layout and the &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt; distance.&lt;/p&gt;

&lt;p&gt;Sounds easy, with couples line of the code. Then I tried to integrate it with my app...&lt;/p&gt;
&lt;h2&gt;
  
  
  Hover is Not Equal to Mouse Enter 🤦‍♂️
&lt;/h2&gt;

&lt;p&gt;We had the very basic usage of the tooltip, show up when your hover on some icons, dismiss when you hover out. I looks pretty well when I test with the desktop devices. When I open the surface, Oh flicking....&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can we just disable tooltip when touch screen detected?&lt;/li&gt;
&lt;li&gt;No, we can't, if you want to use &lt;code&gt;navigator.maxTouchPoints&lt;/code&gt; to detect touch screen, you'll get wrong result on Edge.&lt;/li&gt;
&lt;li&gt;Oh, ok, Edge, alright...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's try to solve it by browser events. Back to the topic on my previous blog &lt;a href="https://dev.to/post/universal-scrubbing-experience-on-web/"&gt;Universal Scrubbing Experience on Web&lt;/a&gt;. In a word, if you try to capture hover actions by &lt;strong&gt;mouseenter&lt;/strong&gt; and &lt;strong&gt;mouseleave&lt;/strong&gt; events, that's a trap.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;PointerEvent&lt;/code&gt; on the browsers supported and use &lt;code&gt;MouseEvent&lt;/code&gt; on the ones which don't have &lt;code&gt;PointerEvent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The trigger handlers finally become like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// if `hover` is specified in trigger `events`&lt;/span&gt;
&lt;span class="nf"&gt;onMouseEnter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// match desktop safari behavior&lt;/span&gt;
  &lt;span class="c1"&gt;// mobile safari won't trigger any mouse event while touching&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PointerEvent&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TouchEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showOverlay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;onPointerEnter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// match desktop/mobile browsers which support PointerEvent&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pointerType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showOverlay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like we're done now? But soon I found there's something wrong...&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, the Size of Trigger and Tooltip Might Change
&lt;/h2&gt;

&lt;p&gt;If just play with hover you won't have this issue, maybe. But triggers' size do change, positioning only on &lt;strong&gt;did mount&lt;/strong&gt; phase is not enough, &lt;strong&gt;did update&lt;/strong&gt; is required as well.&lt;/p&gt;

&lt;p&gt;Then question comes, how do we really know if there's any internal state change happened inside children and overlay components. &lt;br&gt;
If we pass down any prop like &lt;code&gt;onSizeUpdate&lt;/code&gt;, that's kind of tricky no one knows the root cause of resizing is class name changing or due to DOM tree updates.&lt;/p&gt;
&lt;h4&gt;
  
  
  react-bootstrap &amp;lt;OverlayTrigger /&amp;gt;
&lt;/h4&gt;

&lt;p&gt;After checking how the popular UI components library solving this problem, like react-bootstrap, ant-design, I found that react-bootstrap pass down a function prop called &lt;code&gt;scheduleUpdate&lt;/code&gt; to trigger, that let trigger be able to forcedly enqueue an repositioning task when it's necessary. It's quite convenient, but we need to omit this function prop on trigger when we don't need it or when we spread all props onto it.&lt;/p&gt;

&lt;p&gt;That's kind of inconvenient, since there're still few DOM props like &lt;code&gt;onMouseEnter&lt;/code&gt; and &lt;code&gt;onClick&lt;/code&gt;, been passed to trigger implicitly.&lt;/p&gt;
&lt;h4&gt;
  
  
  ant-design &amp;lt;align /&amp;gt;
&lt;/h4&gt;

&lt;p&gt;Ant design align component use &lt;strong&gt;ResizeObserver&lt;/strong&gt; to track trigger size change. Unfortunately &lt;strong&gt;ResizeObserver&lt;/strong&gt; is not widely supported. When I write this post, &lt;a href="https://caniuse.com/#feat=resizeobserver" rel="noopener noreferrer"&gt;https://caniuse.com/#feat=resizeobserver&lt;/a&gt; shows that ResizeObserver is only supported on latest tech preview version and mobile safari doesn't support it. Ant design included a polyfill for it to get rid fo resize observer usage. &lt;/p&gt;

&lt;p&gt;If we don' care about the bundle size very much, resize observer polyfill could be a choice. However I do care :) ...&lt;/p&gt;

&lt;p&gt;Finally I came out a idea, that we use ResizeObserver when it's available, and fallback to &lt;strong&gt;MutationObserver&lt;/strong&gt; on some unsupported browsers. With MutationObserver, the approach is to monitor cache the size and invoke callback when size gets changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onMeasure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResizeObserver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ResizeObserver&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onMeasure&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nx"&gt;ro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ro&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cachedSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleMutate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;cachedSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cachedSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;
        &lt;span class="nx"&gt;cachedSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;
        &lt;span class="nf"&gt;onMeasure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleMutate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;mob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mutationObserverOption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;mob&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we keep the API as simple as possible, and make the implementation as small as possible. I think we solve the most annoying issue :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Repo &amp;amp; Demo
&lt;/h2&gt;

&lt;p&gt;Checkout the source code on &lt;a href="https://github.com/huozhi/react-overlay-trigger" rel="noopener noreferrer"&gt;https://github.com/huozhi/react-overlay-trigger&lt;/a&gt; or use it directly with &lt;code&gt;npm install --save react-overlay-trigger&lt;/code&gt;.&lt;br&gt;
I also provide a playground that you can try it with different devices/browsers. &lt;a href="https://huozhi.github.io/react-overlay-trigger/" rel="noopener noreferrer"&gt;https://huozhi.github.io/react-overlay-trigger/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://bundlephobia.com/result?p=react-overlay-trigger" rel="noopener noreferrer"&gt;bundlephobia&lt;/a&gt; we can see it's only 2kb after minimized and gzipped. Small enough, and fit for general situations. Whatever you want to pop with your trigger components.&lt;/p&gt;

&lt;p&gt;Hope you'll like it, issues &amp;amp; PRs are welcomed!&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>ux</category>
    </item>
    <item>
      <title>Universal Scrubbing Experience on Web</title>
      <dc:creator>huozhi</dc:creator>
      <pubDate>Sun, 22 Dec 2019 06:36:57 +0000</pubDate>
      <link>https://dev.to/huozhi/universal-scrubbing-experience-on-web-4ja3</link>
      <guid>https://dev.to/huozhi/universal-scrubbing-experience-on-web-4ja3</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F4800338%2F58635624-eecb3e00-8320-11e9-9b94-9319fd778aef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F4800338%2F58635624-eecb3e00-8320-11e9-9b94-9319fd778aef.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was surprised that the first touch device hard to deal with is Edge browser on Windows tablet. I though it was just like chrome. It had been years that the only touch events I had to handle is &lt;code&gt;TouchEvent&lt;/code&gt;. Most web applications have simple interactions: just click. If you can click with mouse, you can navigate, trigger popup or submit forms. &lt;/p&gt;

&lt;p&gt;Think about it, while you're watching a video on tablet, or with your cell phone, you want to jump further seconds but there is only clicks can be achieved, will you feel disappoint? To protect user experience such as scrubbing the timeline or playhead (indicator) for seeking the position, we need scrub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ahhhh...Cross Browser?
&lt;/h2&gt;

&lt;p&gt;Click is across browser, browser doesn't care about your mouse, but screen does care about your finger. Usually in the devices supporting touch events, it has &lt;code&gt;touchstart&lt;/code&gt;, &lt;code&gt;touchmove&lt;/code&gt;, &lt;code&gt;touchend&lt;/code&gt; and &lt;code&gt;touchcancel&lt;/code&gt;. But after chrome introducing &lt;a href="https://developers.google.com/web/updates/2016/10/pointer-events" rel="noopener noreferrer"&gt;pointer events&lt;/a&gt;, dev noticed that there were still ways to handle similar stuff like mouse events.&lt;/p&gt;

&lt;p&gt;just change all mouse events prefix to &lt;code&gt;pointer&lt;/code&gt;, you'll get &lt;code&gt;pointerup&lt;/code&gt;, &lt;code&gt;pointermove&lt;/code&gt;, etc. Also, with the ability to detect is fired by finger, mouse or a pen.&lt;/p&gt;

&lt;p&gt;Some of our requirements have different behavior of scrubs on player timeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when you do touch scrub, the indicator on timeline move, but mouse doesn't&lt;/li&gt;
&lt;li&gt;when you do mouse hover and keep moving, the indicator move as well&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hover vs touch scrubs. Here we gotta detect the type of input. How?&lt;/p&gt;

&lt;h2&gt;
  
  
  First Failed Try
&lt;/h2&gt;

&lt;p&gt;According to the &lt;a href="https://www.w3.org/TR/pointerevents/#releasing-pointer-capture" rel="noopener noreferrer"&gt;spec of pointer events on w3c&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;You can see a ordered sequence of events firing. For example, when you do click, then following events got fired.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mousemove
pointerover
pointerenter
mouseover
mouseenter
pointerdown
mousedown
Zero or more pointermove and mousemove events, depending on movement of the pointer
pointerup
mouseup
click
pointerout
pointerleave
mouseout
mouseleave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observing that the corresponding pointer events are fired before mouse events, every event starts with a &lt;code&gt;mouseover&lt;/code&gt;. I want to handle all events inside one function so we decided that maintain the continuous pointer type during an finger/mouse interaction. &lt;br&gt;
Since Safari doesn't have pointer events, I decided to use &lt;code&gt;TouchEvent&lt;/code&gt; as replacement. The we could just listen to mouse events, every mouse event instance expect first one will be set with a attribute &lt;code&gt;pointerType&lt;/code&gt; to tell you the input source.&lt;/p&gt;

&lt;p&gt;Actually, the idea that setting event with &lt;code&gt;pointerType&lt;/code&gt; is came from &lt;em&gt;Netflix&lt;/em&gt;'s website. I reversed engineering some compressed js code (not fully mangled) I found the solution.&lt;/p&gt;

&lt;p&gt;But finally i realize that won't work, 'cause mouse events are not always fired on touch devices.&lt;br&gt;
Netflix use native controls in Safari if watching a content without login. (I don't know what it will change if you login, but I guess it won't change too much? the code is there). They use a product level solution to resolve this friction.&lt;/p&gt;

&lt;p&gt;Smart approach I'd say so, but my product didn't tell me to enable native video controls while user watching on our site. So I tested on different devices/browsers and made a list of status of events support. See:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Browser &amp;amp; Action&lt;/th&gt;
&lt;th&gt;PointerEvent&lt;/th&gt;
&lt;th&gt;MouseEvent&lt;/th&gt;
&lt;th&gt;TouchEvent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;chrome + touch&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edge + touch&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;safari + touch&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chrome + mouse&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edge + mouse&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;safari + mouse&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Is there any way to handle all these stuffs universally?&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross Browser Scrub Solution
&lt;/h3&gt;

&lt;p&gt;Scrubs have there types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scrub with finger touch&lt;/li&gt;
&lt;li&gt;scrub with mouse drag&lt;/li&gt;
&lt;li&gt;mouse over and move on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One behavior may happen on these two composed from three scenarios above , or event only one.&lt;br&gt;
Our approaching is to separate these actions by configurations, make them independent.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Developer Needs?
&lt;/h3&gt;

&lt;p&gt;Usually we only care about the triggered callbacks, start / move / end are enough. They could be designed as events or handlers.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Separate These Actions?
&lt;/h3&gt;

&lt;p&gt;Let's go through the movements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hovering elements with mouse: enter, move, leave&lt;/li&gt;
&lt;li&gt;Scrubbing through: pointer down / move / pointer up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that's it. Due to different status of events support, we need to handle them in proper way. The handlers may receive event which is instance of &lt;code&gt;PointerEvent&lt;/code&gt;, &lt;code&gt;MouseEvent&lt;/code&gt; or &lt;code&gt;TouchEvent&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show Code
&lt;/h2&gt;

&lt;p&gt;If you want to see the details of implementation check &lt;a href="https://github.com/huozhi/fpoint" rel="noopener noreferrer"&gt;huozhi/fpoint&lt;/a&gt;. The API is pretty simple and easy to get started. You can also check the &lt;a href="https://huozhi.github.io/fpoint/scrub" rel="noopener noreferrer"&gt;demo&lt;/a&gt; on different browsers/devices.&lt;/p&gt;

&lt;p&gt;Feel free to submit issues or questions on the repo. :)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
