I'm pretty sure HTTP caching headers don't provide a guarantee that they will be stored for any length of time at all, the browser is free to empty it at will. They also don't guarantee that all your cached assets will be valid with respect to each other (ie your browser might cache an HTML file but not the content it refers to).
Most importantly, though, you can't simply disable HTTP caching when you notice that you're back online. Those cache entries are there now, and they're hard to get out. Service Workers, on the other hand, will just transparently stop intercepting stuff.
With respect to localStorage, I think the idea is that you absolutely can do almost an entire offline mode with it, with the caveat that your content (html, js, css, images) won't get stored there, so you can't load the page at all!
Also I think there are considerations about how difficult it is for many front end architectures to implement this transparent fallback themselves. If you're a Redux or ClojureScript type of programmer it might be utterly obvious and even trivial to implement (just add a simple middleware), but sadly not everyone is, and a lot of the offline logic would have to be implemented everywhere in your codebase. A service worker lets you do it all in one place.
It's a toss-up whether to use localStorage or a service worker if you could viably do either. Blanket logic over all your HTTP calls would be easier in a service worker, and more customized logic would be easier in localStorage, I'm guessing.